创建线程的4种方法和线程的生命周期
线程的启动和运行
方法一:使用start()方法:用来启动一个线程,当调用start方法后,JVM会开启一个新线程执行用户定义的线程代码逻辑。
方法二:使用run()方法:作为线程代码逻辑的入口方法。run方法不是由用户程序来调用的,当调用start方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run方法去执行具体的用户线程代码。
start方法用于启动线程,run方法是用户逻辑代码执行入口。
1.创建一个空线程
main {
Thread thread = new Thread();
thread.start();
}
程序调用start方法启动新线程的执行。新线程的执行会调用Thread的run方法,该方法是业务代码的入口。查看一下Thread类的源码,run方法的具体代码如下:
public void run() {
if(this.target != null) {
this.target.run();
}
}
这里的target属性是Thread类的一个实例属性,该属性非常重要,后面会讲到。在Thread类中,target属性默认为空。在这个例子中,thread属性默认为null。所以在thread线程执行时,其run方法其实什么也没做,线程就执行完了。
2.继承Thread类创建线程
new Thread(() -> {
System.out.println(1);
}).start();
3.实现Runnable接口创建线程
class TestMain implements Runnable {
main {
new Thread(TestMain::new).start();
}
@Override
public void run() {
System.out.println(12);
}
}
4.使用Callable和FutureTask创建线程
前面的Thread和Runnable都不能获取异步执行的结果。为了解决这个问题,Java在1.5之后提供了一种新的多线程创建方法:Callable接口和FutureTask类相结合创建线程。
1.Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable接口是一个泛型接口,也是函数式接口。其唯一的抽象方法call有返回值。
Callable能否和Runnable一样,作为Thread实例的target使用呢?
答案是不可以。因为Thread的target属性类型为Runnable,所以一个在Callable与Thread之间搭桥接线的重要接口即将登场。
2.RunnableFuture接口
这个重要的接口就是RunnableFuture接口,他实现了两个目标,一是可以作为Thread实例的target实例,二是可以获取异步执行的结果。
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
通过源码可以看出:RunnableFuture是通过继承Runnable和Future来实现上面2个目标的。
3.Future接口
Future接口至少提供了三大功能:(1)取消执行中的任务(2)判断任务是否完成(3)获取任务的执行结果
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;
}
- V get():获取异步任务执行的结果,这个方法的调用是阻塞性的。如果异步任务没有执行完成,会一直被阻塞直到任务执行完成。
总体来说,Future是一个对异步任务进行交互、操作的接口。但是Future只是一个接口,它没有办法直接完成对异步任务的操作,JDK提供了一个默认的实现类-----FutureTask。
4.FutureTask类
FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。但是FutureTask类不仅实现了Future接口,还实现了Runnable接口,更精准的说FutureTask类实现了RunnableFuture接口。
前面提到RunnableFuture接口很关键,既可以作为Thread线程实例的target目标,又可以获取并发任务执行的结果,是Thread与Callable之间一个非常重要的搭桥角色。
关系图如下:
可以看出,FutureTask既能作为一个Runnable类型的target,又能作为Future异步任务来获取Callable的计算结果。
FutureTask是如何完成多线程的并发执行、任务结果的异步获取呢?他的内部有一个Callable类型的成员:
private Callable<V> callable;
Callable实例属性用来保存并发执行的Callable类型的任务,并且Callable实例属性需要在FutureTask实例构造时初始化。FutureTask实现了Runnable接口,在run方法的实现中会执行Callable的call方法。
FutureTask内部有一个outcome实例属性用于保存执行结果。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
使用Callable和FutureTask创建线程的具体步骤
(1)创建Callable接口的实现类,并实现call方法,编写好具体逻辑。
(2)使用Callable实现类的实例构造一个FutureTask实例。
(3)使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例
(4)调用Thread实例的start方法启动新线程,内部执行过程:启动Thread实例的run方法并发执行后,会执行FutureTask实例的run方法,最终会并发执行Callable实现类的call方法。
(5)调用FutureTask对象的get方法阻塞的获得结果。
public static void main(String[] args) {
new Thread(new FutureTask<>(() -> {
int i = 1;
return i;
})).start();
}
5.线程池创建线程
1.线程池的创建与执行目标提交
创建一个固定3个线程的线程池。
private static ExecutorService pool = Executors.newFixedThreadPool(3);
向ExecutorService线程池提交异步执行target目标任务的常用方法有:
// 方法一:无返回
void execute(Runnable command);
// 返回一个Future实例
<T> Future<T> submit(Callable<T> task);
// 也是返回Future实例
Future<?> submit(Runnable task);
2.线程池使用实战
public class TestMain {
public static ExecutorService pool = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws ExecutionException, InterruptedException {
pool.execute(() -> {
int i = 1;
});
final Future<?> submit = pool.submit(() -> {
int i = 1;
return i;
});
Integer o = (Integer) submit.get();
System.out.println("o: " + o);
AtomicInteger ans = new AtomicInteger();
final Future<AtomicInteger> submit1 = pool.submit(new FutureTask<>(() -> {
int i = 1;
ans.set(i);
return ans;
}), ans);
System.out.println("ans: " + submit1.get());
pool.shutdown();
}
Lambda中不允许使用局部变量:因为使用的是局部变量的副本,对局部变量本身不起作用,所以不能使用。但是可以使用引用类型的变量,比如原子类的ans变量,这才会对原值产生影响。
execute与submit区别:
(1)接收参数不一样。
(2)submit有返回值,execute没有返回值。
说明:实际生产环境禁止使用Executors创建线程池。
线程的核心原理
1.线程的生命周期
public static enum State {
NEW, 新建
RUNNABLE, 可执行
BLOCKED, 阻塞
WATTING, 等待
TIMED_WAITING, 超时等待
TERMINATED; 终止
}
在定义的6种状态中,有4种比较常见的状态,他们是:NEW、RUNNABLE、TERMINATED、TIMED_WAITING。
- NEW状态:创建成功但是没有调用start方法启动的线程实例都处于NEW状态。
当然,并不是线程实例的start方法一调用,其状态就从NEW到RUNNABLE,此时并不意味着线程立即获取CPU时间片并且立即执行。
- RUNNABLE状态:前面说到,当调用了线程实例的start方法后,下一步如果线程获取CPU时间片开始执行,JVM将异步调用线程的run方法执行其业务代码。那么在run方法被调用之前,JVM在做什么呢?
JVM的幕后工作和操作系统的线程调度有关。当Java线程示例的start方法被调用后,操作系统中对应线程并不是运行状态,而是就绪状态,而Java线程没有就绪态。就绪态的意思就是该线程已经满足执行条件,处于等待系统调度的状态,一旦被选中就会获得CPU时间片,这时就变成运行态了。
在操作系统中,处于运行状态的线程在CPU时间片用完后,又回到就绪态,等待CPU的下一次调度。就这样,操作系统线程在就绪态和运行态之间被反复调度,知道线程的代码逻辑完成或者异常终止为止。这时线程进入TERMINATED状态。
就绪态和运行态都是操作系统的线程状态。在Java中,没有细分这两种状态,而是将他们二合一,都叫作RUNNABLE状态。这时Java线程状态和操作系统不一样的的地方。
总结:NEW状态的线程实例调用了start方法后,线程的状态变为RUNNABLE。但是线程的run方法不一定会马上执行,需要线程获取了CPU时间片之后才会执行。
- TERMINATED状态:run方法执行完成之后就变成终止状态了,异常也会。
- TIME_WAITING状态:处于等待状态,例如Thread.sleep,Objects.wait,Thread.join等。(主动)
- BLOCKED状态:处于此状态的线程不会占用CPU资源,例如线程等待获取锁,IO阻塞。(被动)
2.线程状态实例
让五个线程处于TIME-WAITING状态,使用Jstack查看。
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 500; j++) {
try {
Thread.sleep(500);
System.out.println(j);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624761800 nid=0x2fcc waiting on condition [0x0000003944bff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@11.0.11/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624762000 nid=0x1d74 waiting on condition [0x0000003944cfe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@11.0.11/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
"Thread-2" #16 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624763800 nid=0x3f08 waiting on condition [0x0000003944dfe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@11.0.11/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
"Thread-3" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624765000 nid=0x2b5c waiting on condition [0x0000003944eff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@11.0.11/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
"Thread-4" #18 prio=5 os_prio=0 cpu=0.00ms elapsed=15.27s tid=0x0000018624768000 nid=0x52f0 waiting on condition [0x0000003944ffe000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@11.0.11/Native Method)
at com.test.TestMain.lambda$main$0(TestMain.java:33)
at com.test.TestMain$$Lambda$14/0x0000000800066840.run(Unknown Source)
at java.lang.Thread.run(java.base@11.0.11/Thread.java:834)
Reference
《Java高并发核心编程》