Java并发编程(二)
主线程与守护线程
- 默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
- 如下为守护线程的演示,设置daemon线程为守护线程,中间sleep两秒,当主线程结束后,不管daemon线程结束未结束,都会强行结束,所以该程序的输出如下,daemon线程并未输出运行结束
public static void main(String[] args) {
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("运行结束...");
}
23:04:04.951 [main] DEBUG t.Test14 - 开始运行...
23:04:05.056 [daemon] DEBUG t.Test14 - 开始运行...
23:04:06.061 [main] DEBUG t.Test14 - 运行结束...
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求
线程的五种状态
- 从操作系统层面来划分,线程分为五种状态,如下
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
Java线程的六种状态
- 从Java API层面来讲,根据 Thread.State 枚举,线程分为六种状态
状态说明
- 初始状态(NEW)
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。 - 就绪状态(RUNNABLE之READY)
- 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
- 调用线程的start()方法,此线程进入就绪状态。
- 锁池里的线程拿到对象锁后,进入就绪状态。
- 运行中状态(RUNNABLE之RUNNING)
- 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一的一种方式。
- 阻塞状态(BLOCKED)
- 阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
- 等待(WAITING)
- 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
- 超时等待(TIMED_WAITING)
- 处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
- 终止状态(TERMINATED)
- 当前线程所有代码运行完毕,会进入TERMINATED状态,在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
状态转换
假设有一个线程t
- 依照上图,情况1,
NEW->RUNNABLE
- 当线程调用start方法时,由
NEW->RUNNABLE
- 当线程调用start方法时,由
- 情况2,
RUNNABLE <--> WAITING
- 当t线程用
synchronized(obj)
获取了对象锁之后- 调用
obj.wait()
方法时,线程从RUNNABLE --> WAITING
- 调用
obj.notify() , obj.notifyAll() , t.interrupt()
时- 竞争锁成功,t 线程从
WAITING --> RUNNABLE
- 竞争锁失败,t 线程从
WAITING --> BLOCKED
- 竞争锁成功,t 线程从
- 调用
- 当t线程用
- 情况3,
RUNNABLE <--> WAITING
- 当前线程调用
t.join()
方法时,当前线程从RUNNABLE --> WAITING
- 注意是当前线程在t线程对象的监视器上等待
- t线程运行结束,或调用了当前线程的
interrupt()
时,当前线程从WAITING --> RUNNABLE
- 当前线程调用
- 情况4,
RUNNABLE <--> WAITING
- 当前线程调用
LockSupport.park()
方法会让当前线程从RUNNABLE --> WAITING
- 调用
LockSupport.unpark(目标线程)
或调用了线程 的interrupt()
,会让目标线程从WAITING -->RUNNABLE
- 当前线程调用
- 情况 5
RUNNABLE <--> TIMED_WAITING
- t 线程用
synchronized(obj)
获取了对象锁后- 调用
obj.wait(long n)
方法时,t 线程从RUNNABLE --> TIMED_WAITING
- t 线程等待时间超过了 n 毫秒,或调用
obj.notify() , obj.notifyAll() , t.interrupt()
时- 竞争锁成功,t 线程从
TIMED_WAITING --> RUNNABLE
- 竞争锁失败,t 线程从
TIMED_WAITING --> BLOCKED
- 竞争锁成功,t 线程从
- 调用
- t 线程用
- 情况 6
RUNNABLE <--> TIMED_WAITING
- 当前线程调用
t.join(long n)
方法时,当前线程从RUNNABLE --> TIMED_WAITING
- 注意是当前线程在t 线程对象的监视器上等待
- 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的
interrupt()
时,当前线程从TIMED_WAITING --> RUNNABLE
- 当前线程调用
- 情况 7
RUNNABLE <--> TIMED_WAITING
- 当前线程调用
Thread.sleep(long n)
,当前线程从RUNNABLE --> TIMED_WAITING
- 当前线程等待时间超过了 n 毫秒,当前线程从
TIMED_WAITING --> RUNNABLE
- 当前线程调用
- 情况 8
RUNNABLE <--> TIMED_WAITING
- 当前线程调用
LockSupport.parkNanos(long nanos)
或LockSupport.parkUntil(long millis)
时,当前线
程从RUNNABLE --> TIMED_WAITING
- 调用
LockSupport.unpark(目标线程)
或调用了线程 的interrupt()
,或是等待超时,会让目标线程从TIMED_WAITING--> RUNNABLE
- 当前线程调用
- 情况 9
RUNNABLE <--> BLOCKED
- t 线程用
synchronized(obj)
获取了对象锁时如果竞争失败,从RUNNABLE --> BLOCKED
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从
BLOCKED --> RUNNABLE
,其它失败的线程仍然 BLOCKED
- t 线程用
- 情况 10
RUNNABLE <--> TERMINATED
- 当前线程所有代码运行完毕,进入
TERMINATED
- 当前线程所有代码运行完毕,进入
黑马程序员多线程课程:JUC并发编程全套教程