java并发编程的艺术-第四章笔记
线程简介
什么是线程
- 现代操作系统调度的最小单元
- 轻量级进程
线程的状态
- NEW: 初始状态,线程被构建,但是还没有调用start()方法
- RUNNABLE: 运行状态,Java线程将操作系统的就绪和运行两种状态,笼统的称做“运行中“
- BLOCKED: 阻塞状态,表示线程阻塞于锁
- WAITING: 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或终端)
- TIME_WAITING: 超市等待状态,该状态不同于WAITING,它是可以在制定的时间自行返回的
- TERMINATED: 终止状态,表示当前线程以执行完毕
ps: 线程状态流转图P89,图4-1
Daemon线程
- 当一个java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出
- 通过调用Thread.setDaemon(true)将当前线程设置为Daemon线程
ps: daemon线程的finally块不一定会被执行,在虚拟级退出的时候,Jvm中的所有Daemon线程都需要立即终止。
启动和终止线程
- 通过线程的start方法启动
- 随着run方法执行完毕,终止
构造线程
- 所属线程组
- 线程优先级
- 是否是Daemon线程
一个新构造的线程对象是由其parent线程来进行空间分配的,child线程继承了parent的一些属性(是否为daemon、优先级、加载资源的contextClassLoader、可继承的ThreadLoacl),同时会分配一个惟一的ID来标识这个child线程。在堆内存等待运行。
启动线程
调用start()方法启动线程,线程start()方法的含义是:当前线程(即parent)同步告知Java虚拟机,只要线程规划其空闲,应立即启动调用start()方法的线程。
?: 线程规划器是什么,是线程调度器么
理解中断
- 线程的一个标识位属性
- 其他线程通过调用该线程的interrupt()方法对其进行中断操作
- 线程通过方法isInterrupt()来判断是否被中断
- 可以调用静态方法Thread.interrupted()方法对当前线程中的中断标识进行复位
- 如果该线程已经处于终结状态,即使被中断过,在调用该线程对象的isInterrupted()时依旧会返回false
ps: 从java的api中,许多声明抛出InterruptedException的方法(如Thread.sleep(long millis)),这些方法抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清楚,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
过期的suspend()、resume()和stop()
暂停、恢复和停止
- suspend不会释放已有的资源,而是占用资源进入睡眠状态,可能会死锁
- stop()方法在终结一个线程时不会保证资源正常释放,通常没有给予线程完成释放资源工作的机会
ps:也正是因为这些副作用,这些方法才被标注为不建议的过期方法
这些方法才被标注为不建议的过期方法
如何安全终止线程
- 中断操作适合用来取消或停止任务
- 可以利用一个volatile的boolean变量来控制是否需要停止任务,并终止该任务。
ps:通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断的将线程停止,这样更加安全和优雅。
线程间通信
线程间通信
volatile和synchronized关键字
- 只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性
- 本质上都是对一个对象的监视器(monitor)进行获取,而这个过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器
- 没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态
等待通知机制
循环检查变量是否符合预期
while(value != desire) {
Thread.sleep(1000);
}
doSomething();
这种方式存在这如下问题:
- 难以确保及时性;
- 难以降低开销
等待/通知
- 使用wait()、notify()、notifyAll()时需要先对调用对象加锁
- 调用wait()方法后,线程状态由RUNNIING变为WAITING,并将当前线程放置到对象的等待队列。
- notify()或notifyAll方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
- notify()方法将等待队列中的一个线程从等待队列移动到同步队列,被移动线程的状态由WAITING变成BLOCKED。
- 从wait()方法返回的前提是获得了调用对象的锁
等待/通知的经典范式
等待方遵循如下原则:
- 获取对象的锁
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
- 条件满足则执行对应逻辑
synchroized(对象){
while(条件不满足){
对象.wait();
}
// todo
}
通知方遵循如下原则
- 获得对象的锁
- 改变条件
- 通知所有等待在对象上的线程
synchronized(对象) {
改变条件
对象.notifyAll();
}