死磕 java线程系列之线程的生命周期
(手机横屏看源码更方便)
注: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状态了,很神奇。
彩蛋
其实,本来这篇是准备写线程池的生命周期的,奈何线程的生命周期写了太多,等下一篇我们再来一起学习线程池的生命周期吧。
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。
死磕 java线程系列之线程的生命周期的更多相关文章
- 死磕 java同步系列之AQS起篇
问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...
- 死磕 java同步系列之volatile解析
问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java ...
- 死磕 java同步系列之自己动手写一个锁Lock
问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之zookeeper分布式锁
问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...
- 死磕 java同步系列之redis分布式锁进化史
问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...
- 死磕 java同步系列之终结篇
简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...
- 死磕 java同步系列之StampedLock源码解析
问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...
- 死磕 java同步系列之AQS终篇(面试)
问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...
随机推荐
- CodeForces - 556C Case of Matryoshkas (水题)
Andrewid the Android is a galaxy-famous detective. He is now investigating the case of vandalism at ...
- java设计模式(二)单例模式,一生只爱一人,只争一朝一夕
单例模式:保证一个类在内存中的对象唯一,有且仅能实例化一次.(如多个代码块需要读取配置文件,or开启事务,orjdbc读取数据源就是个经典例子)参考:吟啸且徐行 实现步骤: 私有构造方法.保证唯一的 ...
- 骚年,如果你还不懂一些java常识?中了奖也无法兑换
今天下午约着几个朋友一起去看叶问4,结果碰到了一个有趣的事情,正好和java有关所以写一篇文章来记录一下. 事件:我和朋友小李.小王一起去看电影 时间:2019/12/21 地点:H市某家电影院 起因 ...
- OpenSSL 自述
1995 年, Eric A. Young 和 Tim J. Hudson 发明了 SSLeay,它是 SSL(Open-source Secure Sockets) 协议的实现.1998 年,You ...
- deinit 没执行
写了一个自定义的UIView,其中包含代理 然后设置UIViewController为此UIView的代理 结果UIViewController里的deinit没执行,导致内存 ...
- 区块链技术驱动金融.mobi
链接:https://pan.baidu.com/s/1yY8f_PglsPoudb76nru9Ig 提取码:c58o 想一起学习区块链的朋友可以加好友一个学习哦,共同进步
- 百度大脑IOCR财会票据识别技术接入小程序,快速实现财会票据识别
本文主要介绍iOCR财会票据识别的小程序功能实现. 想了解微信小程序的开发过程,请参看我之前的帖子:<UNIT接入小程序>https://ai.baidu.com/forum/topic/ ...
- 使用Git出现以下错误"Git@github.com: Permission denied (publickey). Could not read from remote repository."解决方案
转载于:https://blog.csdn.net/dotphoenix/article/details/100130424 git@github.com: Permission denied (pu ...
- Cookie与Session会话技术
Cookie与Session会话技术 一.什么是会话 会话:当用户打开浏览器,访问多个WEB资源,然后关闭浏览器的过程,称之为一个会话,选项卡,弹出页面都属于这个会话,且共享同一个session. 二 ...
- DDD 实战记录——实现「借鉴学习计划」
「借鉴学习计划」的核心是:复制一份别人的学习计划到自己的计划中,并同步推送学习任务给自己,并且每个操作都要发送通知给对方. 它们的类图如下: 它们的关系是一对多: // Schedule entity ...