Java线程是Java并发编程的基础,理解Java线程的生命周期对于编写高效、稳定的并发程序至关重要。本文将从两个角度来介绍Java线程的生命周期,并通过代码示例进行验证。
在Java中,线程的创建主要通过两种方式:继承Thread类或实现Runnable接口、Callnablee接口。以下是一个简单的示例:
步骤:
/** * Java中创建线程方式一:继承Thread类 */public class ThreadTest extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } public static void main(String[] args) { ThreadTest threadTest = new ThreadTest(); threadTest.start(); }}
打印结果:
0123456789
步骤:
/** * Java中创建线程方式二:实现Runnable接口 */public class RunnableTest implements Runnable{ @Override public void run() { for (int i = 100; i < 110; i++) { System.out.println(i); } } public static void main(String[] args) { RunnableTest runnableTest = new RunnableTest(); Thread thread = new Thread(runnableTest); thread.start(); }}
打印结果:
100101102103104105106107108109
一般创建线程时,使用上面两种方式居多。但是这两种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable接口可以理解成一段可以调用并返回结果的代码(call方法);
Future接口表示异步任务,是还没有完成的任务给出的未来结果。
所以说Callable用于产生结果,Future用于获取结果。这点可以在源码里面分析得知。
源码分析
Runnable位于java.lang包下,它是一个接口,在它里面声明了一个方法叫做 run():
@FunctionalInterfacepublic interface Runnable { public abstract void run();}
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
再看Callable源码Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():
@FunctionalInterfacepublic interface Callable<V> { V call() throws Exception;}
可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。为什么这么说呢?看了它的源码就知道了。
Future类位于java.util.concurrent包下,它也是一个接口
package java.util.concurrent;public interface Future<V> { /** * 取消任务 */ boolean cancel(boolean mayInterruptIfRunning); /** * 任务是否被取消成功 */ boolean isCancelled(); /** * 任务是否已经完成 */ boolean isDone(); /** * 获取执行结果 */ V get() throws InterruptedException, ExecutionException; /** * 获取执行结果,支持超时 */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}
所以说Future一共给我们提供了三种功能:
但是因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask实现于RunnableFuture接口,这个接口的定义如下:
public interface RunnableFuture<V> extends Runnable, Future<V> { void run();}
可以看到这个接口实现了Runnable和Future接口,接口中的具体实现由FutureTask来实现。这个类的两个构造方法如下 :
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
如上提供了两个构造函数,一个以Callable为参数,另外一个以Runnable为参数。这些类之间的关联允许你基于FutureTask的Runnable特性(因为它实现了Runnable接口),把任务写成Callable,然后封装进一个由执行者调度并在必要时可以取消的FutureTask。
FutureTask可以由执行者调度,它对外提供的方法基本上就是Future和Runnable接口的组合:get()、cancel、isDone()、isCancelled()和run(),而run()方法通常都是由执行者调用,我们基本上不需要直接调用它。
步骤:
示例:
/** * Java中创建线程方式三:Callable和FutureTask结合使用 */public class CallableTest implements Callable{ @Override public Object call() throws Exception { int i = 1000; for ( ; i < 1010; i++) { System.out.println(i); } return 1111; } public static void main(String[] args) { CallableTest callableTest = new CallableTest(); FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest); Thread thread = new Thread(futureTask); thread.start(); try { System.out.println("Result:"+futureTask.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }}
打印结果
1000100110021003100410051006100710081009Result:1111
Java线程的状态可以被划分为五种或六种,这主要取决于你从哪个角度来看。在操作系统的传统线程模型中,线程通常被分为五种状态。
操作系统层面的五种线程状态和JVM的六种线程状态是两个不同层次的概念,它们之间并不是一一对应的关系。
JVM并不关心操作系统线程的实际状态,从JVM看来,等待CPU使用权(操作系统状态为可运行态)与等待I/O(操作系统处于等待状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE状态。因此,操作系统层面的线程状态并不直接影响JVM的线程状态。
这两者的主要区别在于它们关注的焦点不同:操作系统更关注线程对CPU和I/O资源的使用,而JVM更关注线程在Java程序中的行为。
在「JDK1.2之后」,Java线程模型已经确定了基于操作系统原生线程模型实现。因此,目前或者今后的JDK版本中,操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机的线程如何映射,这一点在不同的平台上没有办法达成一致,虚拟机规范中也未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对于Java程序来说,这些差异是透明的。
对应Oracle Sun JDK或者说Oracle Sun JVM而言,它的Windows版本和Linux版本都是使用「一对一的线程模型」实现的。
一对一的线程模型也就是一条Java线程就映射到一条轻量级进程(「Light Weight Process」)中,而一条轻量级线程又映射到一条内核线程(「Kernel-Level Thread」)。我们平时所说的线程,往往就是指轻量级进程(或者通俗来说我们平时新建的java.lang.Thread就是轻量级进程实例的一个"句柄",因为一个java.lang.Thread实例会对应JVM里面的一个JavaThread实例,而JVM里面的JavaThread就应该理解为轻量级进程)。推算这个线程映射关系,可以知道,我们在应用程序中创建或者操作的java.lang.Thread实例最终会映射到系统的内核线程,如果我们恶意或者实验性无限创建java.lang.Thread实例,最终会影响系统的正常运行甚至导致系统崩溃(可以在Windows开发环境中做实验,确保内存足够的情况下使用死循环创建和运行java.lang.Thread实例)。
本文链接:http://www.28at.com/showinfo-26-17526-0.html为什么有些人说JAVA线程五种状态,有些人说六种?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 使用Java AOP实现面向切面编程
下一篇: 为什么 Kafka 的吞吐量那么高?