共计 4667 个字符,预计需要花费 12 分钟才能阅读完成。
(手机横屏看源码更方便)
注:java 源码分析部分如无特殊说明均基于 java8 版本。
简介
大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。
常见的错误 有:就绪状态、运行中状态(RUNNING)、死亡状态、中断状态、只有阻塞没有等待状态、流程图乱画等,最常见的错误就是说线程只有 5 种状态。
今天这篇文章会彻底讲清楚线程的生命周期,并分析 synchronized 锁、基于 AQS 的锁中线程状态变化的逻辑。
所以,对 synchronized 锁和 AQS 原理(源码)不了解的同学,请翻一下彤哥之前的文章先熟悉这两部分的内容,否则肯定记不住这里讲的线程生命周期。
问题
(1)线程的状态有哪些?
(2)synchronized 锁各阶段线程处于什么状态?
(3)重入锁、条件锁各阶段线程处于什么状态?
先上源码
关于线程的生命周期,我们可以看一下 java.lang.Thread.State
这个类,它是线程的内部枚举类,定义了线程的各种状态,并且注释也很清晰。
public enum State {
/**
* 新建状态,线程还未开始
*/
NEW,
/**
* 可运行状态,正在运行或者在等待系统资源,比如 CPU
*/
RUNNABLE,
/**
* 阻塞状态,在等待一个监视器锁(也就是我们常说的 synchronized)* 或者在调用了 Object.wait()方法且被 notify()之后也会进入 BLOCKED 状态
*/
BLOCKED,
/**
* 等待状态,在调用了以下方法后进入此状态
* 1. Object.wait()无超时的方法后且未被 notify()前,如果被 notify()了会进入 BLOCKED 状态
* 2. Thread.join()无超时的方法后
* 3. LockSupport.park()无超时的方法后
*/
WAITING,
/**
* 超时等待状态,在调用了以下方法后会进入超时等待状态
* 1. Thread.sleep()方法后【本文由公从号“彤哥读源码”原创】* 2. Object.wait(timeout)方法后且未到超时时间前,如果达到超时了或被 notify()了会进入 BLOCKED 状态
* 3. Thread.join(timeout)方法后
* 4. LockSupport.parkNanos(nanos)方法后
* 5. LockSupport.parkUntil(deadline)方法后
*/
TIMED_WAITING,
/**
* 终止状态,线程已经执行完毕
*/
TERMINATED;
}
流程图
线程生命周期中各状态的注释完毕了,下面我们再来看看各状态之间的流转:
怎么样?是不是很复杂?彤哥几乎把网上的资料都查了一遍,没有一篇文章把这个流程图完整画出来的,下面彤哥就来一一解释:
(1)为了方便讲解,我们把锁分成两大类,一类是 synchronized 锁,一类是基于 AQS 的锁(我们拿重入锁举例),也就是内部使用了 LockSupport.park()/parkNanos()/parkUntil()几个方法的锁;
(2)不管是 synchronized 锁还是基于 AQS 的锁,内部都是分成两个队列,一个是同步队列(AQS 的队列),一个是等待队列(Condition 的队列);
(3)对于内部调用了 object.wait()/wait(timeout)或者 condition.await()/await(timeout)方法,线程都是先进入等待队列,被 notify()/signal()或者超时后,才会进入同步队列;
(4)明确声明,BLOCKED 状态只有线程处于 synchronized 的同步队列的时候才会有这个状态,其它任何情况都跟这个状态无关;
(5)对于 synchronized,线程执行 synchronized 的时候,如果立即获得了锁(没有进入同步队列),线程处于 RUNNABLE 状态;
(6)对于 synchronized,线程执行 synchronized 的时候,如果无法获得锁(直接进入同步队列),线程处于 BLOCKED 状态;
(5)对于 synchronized 内部,调用了 object.wait()之后线程处于 WAITING 状态(进入等待队列);
(6)对于 synchronized 内部,调用了 object.wait(timeout)之后线程处于 TIMED_WAITING 状态(进入等待队列);
(7)对于 synchronized 内部,调用了 object.wait()之后且被 notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(8)对于 synchronized 内部,调用了 object.wait(timeout)之后且被 notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(9)对于 synchronized 内部,调用了 object.wait(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(10)对于 synchronized 内部,调用了 object.wait()之后且被 notify()了,如果线程无法获得锁(也就是进入了同步队列),线程处于 BLOCKED 状态;
(11)对于 synchronized 内部,调用了 object.wait(timeout)之后且被 notify()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于 BLOCKED 状态;
(12)对于重入锁,线程执行 lock.lock()的时候,如果立即获得了锁(没有进入同步队列),线程处于 RUNNABLE 状态;
(13)对于重入锁,线程执行 lock.lock()的时候,如果无法获得锁(直接进入同步队列),线程处于 WAITING 状态;
(14)对于重入锁内部,调用了 condition.await()之后线程处于 WAITING 状态(进入等待队列);
(15)对于重入锁内部,调用了 condition.await(timeout)之后线程处于 TIMED_WAITING 状态(进入等待队列);
(16)对于重入锁内部,调用了 condition.await()之后且被 signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(17)对于重入锁内部,调用了 condition.await(timeout)之后且被 signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(18)对于重入锁内部,调用了 condition.await(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于 RUNNABLE 状态;
(19)对于重入锁内部,调用了 condition.await()之后且被 signal()了,如果线程无法获得锁(也就是进入了同步队列),线程处于 WAITING 状态;
(20)对于重入锁内部,调用了 condition.await(timeout)之后且被 signal()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于 WAITING 状态;
(21)对于重入锁,如果内部调用了 condition.await()之后且被 signal()之后依然无法获取锁的,其实经历了两次 WAITING 状态的切换,一次是在等待队列,一次是在同步队列;
(22)对于重入锁,如果内部调用了 condition.await(timeout)之后且被 signal()或超时了的,状态会有一个从 TIMED_WAITING 切换到 WAITING 的过程,也就是从等待队列进入到同步队列;
为了便于理解,彤哥这里每一条都分的比较细,麻烦耐心看完。
测试用例
看完上面的部分,你肯定想知道怎么去验证,下面彤哥就说说验证的方法,先给出测试用例。
public class ThreadLifeTest {public static void main(String[] args) throws IOException {Object object = new Object();
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{synchronized (object) {
try {System.out.println("thread1 waiting");
object.wait();
// object.wait(5000);
System.out.println("thread1 after waiting");
} catch (InterruptedException e) {e.printStackTrace();
}
}
}, "Thread1").start();
new Thread(()->{synchronized (object) {
try {System.out.println("thread2 notify");
// 打开或关闭这段注释,观察 Thread1 的状态
// object.notify();【本文由公从号“彤哥读源码”原创】// notify 之后当前线程并不会释放锁,只是被 notify 的线程从等待队列进入同步队列
// sleep 也不会释放锁
Thread.sleep(10000000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}, "Thread2").start();
new Thread(()->{lock.lock();
System.out.println("thread3 waiting");
try {condition.await();
// condition.await(200, (TimeUnit).SECONDS);
System.out.println("thread3 after waiting");
} catch (InterruptedException e) {e.printStackTrace();
} finally {lock.unlock();
}
}, "Thread3").start();
new Thread(()->{lock.lock();
System.out.println("thread4");
// 打开或关闭这段注释,观察 Thread3 的状态
// condition.signal();【本文由公从号“彤哥读源码”原创】// signal 之后当前线程并不会释放锁,只是被 signal 的线程从等待队列进入同步队列
// sleep 也不会释放锁
try {Thread.sleep(1000000);
} catch (InterruptedException e) {e.printStackTrace();
} finally {lock.unlock();
}
}, "Thread4").start();}
}
打开或关闭上面注释部分的代码,使用 IDEA 的 RUN 模式运行代码,然后点击左边的一个摄像头按钮(jstack),查看各线程的状态。
注:不要使用 DEBUG 模式,DEBUG 模式全都变成 WAITING 状态了,很神奇。
彩蛋
其实,本来这篇是准备写线程池的生命周期的,奈何线程的生命周期写了太多,等下一篇我们再来一起学习线程池的生命周期吧。
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。