并发编程的艺术-学习
第四章 Java内存模型
本章着重介绍java并发编程的基础知识,从启动一个线程到线程间不同的通信方式,以及简单的线程池示例。
4.1 线程简介
4.1.1 什么是线程
操作系统调度的最小单元是线程。线程拥有自己的计数器、堆栈、局部变量等属性,能访问共享的内存变量。
4.1.2 为什么使用多线程
更多的处理器核心
更快的响应时间
更好的编程模型
4.1.3 线程优先级
操作系统采用时分的形式调度运行的线程。
线程优先级决定需要分配多少处理器资源(时间片)。优先级越高,分配的时间片越多。
频繁阻塞(休眠或I/O操作)的线程:设置较高优先级
偏重计算(需要较多CPU时间或偏运算)的线程:设置较低优先级
priority:1~10
默认优先级:5
有的操作系统会忽略优先级设置,因此程序正确性不能依赖线程的优先级高低。
4.1.4 线程的状态
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但还没调用start() |
RUNNABLE | 运行状态,包括就绪和运行 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程需要等待其他线程的通知或中断 |
TIME_WAITING | 超时等待状态,在指定时间自行返回 |
TERMINATED | 终止状态,表示线程已经执行完毕 |
4.1.5 Daemon线程
是一种支持型线程
Daemon线程的属性需要在线程其他之前设置
不能依靠finally块确保执行关闭或清理资源(因为java虚拟机中不存在非Daemon线程时,Java虚拟机会退出。
Thread.setDaemon(true)设置线程为Daemon线程
4.2 启动和终止线程
4.2.1 构造线程
初始化构造线程对象:线程所属的线程组、优先级、是否Daemon线程等
新构造的线程对象,由其parent线程进行空间分配,child继承了parent的属性:是否Daemon、优先级、加载资源的contextClassLoader、ThreadLocal等
4.2.2 启动线程
初始化后,调用start()
4.2.3 中断
中断标识位属性
isInterrupted()
Thread.interrupted()
抛出InterruptedException后,中断标识位被清除
4.2.4 过期的suspend()、resume()、stop()
suspend():调用后,线程不会释放资源(锁),而是占着资源进入睡眠,容易引发死锁问题。
stop():终结一个线程时不会保证资源正常释放,导致程序可能工作在不确定状态下。
可以用等待/通知机制替代
4.2.5 安全地终止线程
通过标识位或者中断操作的方式终止线程,使线程在终止时有机会清理资源,更安全
4.3 线程间通信
4.3.1 volatile和synchronized
volatile修饰成员变量,保证所有线程对变量访问的可见性。共享内存。
synchronized修饰方法、代码块,保证线程对变量访问的可见性、排他性。
同步块的实现monitorenter和monitorexit指令,同步方法实现修饰符ACC_SYNCHRONIZED(隐式)。本质是对象的监视器(monitor)。
任意线程对Object的访问,首先获得Object的监视器,获取失败,则进入同步队列,线程状态变为BLOCKED;当访问Object的前驱释放锁,则该释放操作唤醒阻塞在同步队列中的线程,重新尝试获取监视器。
4.3.2 等待/通知机制
指线程A调用了对象O的wait()方法进入等待状态,而线程B调用了对象O的notify()/notifyAll(),线程A收到通知后从对象O的wait()返回,进而执行后续操作。
调用wait()、notify()、notifyAll()注意细节:
1)需先调用对象加锁(如synchronized)
2)调用wait()后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
3)notify()/notifyAlll()调用后,等待线程依旧不会从wait()返回,需要调用notify()/notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回
4)notify()将等待队列中的一个线程移到同步队列;notifyAll()将等待队列中所有线程全部移到同步队列,被移动的线程由WAITING变为BLOCKED
5)从wait()方法返回前提是,获得调用对象的锁
4.3.3 等待/通知的经典范式
等待:
1)获得对象的锁
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
3)条件满足则执行对应的逻辑
通知:
1)获得对象的锁
2)改变条件
3)通知等待在对象上的线程
4.3.4 管道输入/输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介是内存。
4种具体实现:PipedOutputStream、PipedInputStream、PipedReader、PipedWriter
out.connect(in)
4.3.5 Thread.join()的使用
当前线程等待thread线程终止之后才从thread.join()返回。保证线程执行顺序。
4.3.6 ThreadLocal的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。附在线程上,可以根据ThreadLocal对象查询到绑定在这个线程上的值。
理解参考: https://www.jianshu.com/p/98b68c97df9b
4.4 线程应用实例
4.4.1 等待超时模式
4.5 本章小结
启动和终止线程以及线程状态,线程通信方式,示例。