本文主要讲线程在基于synchronized
关键字,以及调用Thread
类各个方法,各种情况下的一些状态。
线程有哪些状态?
在Thread
类中有一个枚举State
,描述线程的6个状态:
public enum State {
NEW,
RUNNABLE,
WAITING,
TIMED_WAITING,
BLOCKED,
TERMINATED
}
线程状态是如何改变的?
这里结合synchronized
关键字,写的一个例子:
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
/**
* 初始化线程对象,会设置线程组,父线程,线程优先级,是否是daemon线程
*/
Thread t = new Thread(() -> {
/**
* 第三步:
* 线程需要等待获取到锁,此时线程状态是 BLOCKED
* 并将线程对象加入到synchronized锁内部的entrySet中
* 详见子路老师的博客:https://blog.youkuaiyun.com/java_lyvee/article/details/110996764
* PS:其实关于entrySet这个概念,我在jdk官方文档并没有找到,这里存质疑
*/
synchronized (object) {
/**
* 第六步:
* 各个线程都获取到了锁,并开始执行任务,此时状态是 RUNNABLE
* 注意:最后获取到锁的线程,反而最先执行!因为synchronized锁内部维护了一个entrySet,遵循后进先出的原则
*/
System.out.println(Thread.currentThread().getName() + "开始任务..." + Thread.currentThread().getState().name());
try {
/**
* 第七步:
* 各个线程开始释放锁,此时线程状态是 WAITING
* 此时线程对象会被加入到synchronized维护的waitSet中
* 详见jdk官方文档:https://docs.oracle.com/javase/specs/jls/se12/html/jls-17.html
*/
object.wait();
} catch (InterruptedException e) {
/**
* 第十一步:
* 获取到锁,捕捉到中断异常,此时线程状态是: RUNNABLE
*/
}
System.out.println(Thread.currentThread().getName() + "结束任务..." + Thread.currentThread().getState().name());
}
}, "线程" + i);
list.add(t);
}
System.out.println();
/**
* 第一步
* main线程获取到锁
*/
synchronized (object) {
for (Thread t : list) {
/**
* 第二步:
* new了线程对象,那么此时线程状态是 NEW
*/
System.out.println("启动前" + t.getName() + "状态:" + t.getState().name());
/**
* 启动线程,如果线程状态不是NEW,则会报IllegalThreadStateException异常
* 调用本地方法start0() ----可下载openjdk源码,方法调用具体位置:src/hotspot/share/prims/jvm.cpp JVM_ENTRY()方法
* 1.在C++层面会做一些校验,设置线程栈大小
* 2.真正意义上创建一个线程(向操作系统申请创建线程)
* 3.启动线程
*/
t.start();
/**
* 由于启动线程,需要向操作系统申请创建线程,
* 虽然操作系统的线程调度,是纳秒级别,但还是有可能新申请创建的线程,会延后才能真正创建好
* 甚至有可能说,在main线程释放锁之后,操作系统的线程才被创建好,就可能状态直接会到 RUNNABLE
* 故这里在debug的时候,可以进行一个睡眠
* 读者可以屏蔽此代码,多增加线程数量进行测试,挺有意思的~
*/
TimeUnit.NANOSECONDS.sleep(1);
/**
* 第三步:
* 由于main线程已经获取到锁,5个子线程的run()都在等待获取锁,所以这时候线程状态是 BLOCKED
*/
System.out.println("启动后" + t.getName() + "线程状态-------" + t.getState().name());
}
/**
* 第四步,main线程执行完synchronized代码块之后,释放锁
*/
}
System.out.println();
/**
* 第八步:
* main线程再次获取到锁
*/
synchronized (object) {
System.out.println();
for (Thread t : list) {
System.out.println("中断前" + t.getName() + "状态:" + t.getState().name());
/**
* 第九步:
* 调用线程中断方法,此时线程状态 WAITING -> BLOCKED
* 这里并没有使用 object.notify()或者object.notifyAll()唤醒线程
* 因为这两个方法唤醒的线程是无法掌控的
*/
t.interrupt();
/**
* 由于 interrupt() 也涉及到操作系统到线程调度,这里也需要短暂睡眠,否则线程状态无法及时更新打印
*/
TimeUnit.NANOSECONDS.sleep(1);
System.out.println("中断后" + t.getName() + "线程状态-------" + t.getState().name());
}
System.out.println();
/**
* 第十步,main线程执行完synchronized代码块之后,释放锁
*/
}
}
运行结果:
启动前线程0状态:NEW
启动后线程0线程状态-------BLOCKED
启动前线程1状态:NEW
启动后线程1线程状态-------BLOCKED
启动前线程2状态:NEW
启动后线程2线程状态-------BLOCKED
启动前线程3状态:NEW
启动后线程3线程状态-------BLOCKED
启动前线程4状态:NEW
启动后线程4线程状态-------BLOCKED
线程4开始任务...RUNNABLE
线程3开始任务...RUNNABLE
线程2开始任务...RUNNABLE
线程1开始任务...RUNNABLE
线程0开始任务...RUNNABLE
中断前线程0状态:WAITING
中断后线程0线程状态-------BLOCKED
中断前线程1状态:WAITING
中断后线程1线程状态-------BLOCKED
中断前线程2状态:WAITING
中断后线程2线程状态-------BLOCKED
中断前线程3状态:WAITING
中断后线程3线程状态-------BLOCKED
中断前线程4状态:WAITING
中断后线程4线程状态-------BLOCKED
线程4结束任务...RUNNABLE
线程3结束任务...RUNNABLE
线程2结束任务...RUNNABLE
线程1结束任务...RUNNABLE
线程0结束任务...RUNNABLE
如果读者有心看到此篇文章,建议把这个demo拷贝下来,多多进行测试和调试,把线程数设置大一些,比如50或者100,这里我是为了方便打印显示日志才设置的线程数为5.
线程状态图解
最终还是得来一张图作为总结: