本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。
1) 进程与线程
进程:Program app,如:qq.sh
线程:即一个进程里,最小的执行单元;进程中不同的执行路径
1.1) 启动线程方式
严谨来说只有两种方式,第三种线程池实际上也是用的 Thread 和 Runnable
- Thread
- Runnable
- new ThreadPoolExecutor
1.2) 线程状态
可以通过 target.getState
获取线程状态
- New
- Runnable
- Ready
- Running
- TimedWaiting
- Waiting
- Blocked
- Teminatted
- Terminated
1.3) 线程基础 API
- sleep
- yield(): 将当前线程丢回等待队列
- target.join: join 到 target 线程中,target 执行后继续执行当前线程
- stop: 建议让线程正常结束,不建议使用 stop,容易产生线程的不一致。
- target.Interrupt: 向 target 线程发送 interrupt 信号
- target 处于非阻塞状态时:target 中可以根据
Thread.currentThread().isInterrupted()
来捕获 interrupt 信号 - target 处于阻塞状态时:target 可以通过捕获 InterruptedException 异常来处理 Interrupt 信号
- target 处于非阻塞状态时:target 中可以根据
1.4) Synchronized
给目标加锁,需要一个锁对象,各种使用方式:
- 代码块:手动指定锁对象
- 标注普通方法:使用 this 为锁对象
- 标注 static 方法:使用当前类对象 xxx.class
1.4.1) 坑
锁对象避免使用基础的数据类型:String、Integer、Long,自己体会为何 -。-
1.4.2) 重入
Synchronized 是可重入锁
1.4.3) 异常
当 Synchronized 域中抛出异常时,HotSpot 中锁会被释放,要注意一致性的处理
1.4.4) 实现
JVM 没有规定如何实现 Synchronized,以下都是 HotSpot 的实现:
如何确定一个 Object 是否上锁,是通过 Object head 中的 MarkWord 实现 ,在 head 中占了 64bit,其中有 2bit 来标识此对象是否被锁定、和使用的锁类型。
在 JDK1.5 之前,Synchronized 都是使用 OS 锁(重量),从 1.5 开始,引入了锁升级的概念:
- 偏向锁
如果锁对象只被某个线程访问,默认不加锁,只在 MarkWord 中记录该线程号,即偏向锁 - 自旋锁
若有其他线程访问这把锁,升级为自旋锁 - 重量级锁
- 1.5 时,自旋 10次 后未拿到锁,锁升级
- 1.6 引入适应性自旋锁,大体上根据,当同时有超过
CPU线程数 / 2
的线程处于等待锁状态时,锁升级
当加锁代码,执行时间短、线程数少时使用自旋。
当加锁代码,执行时间长、线程数多时使用 OS 锁。
1.4.5) 优化
- 细化 synchronized 域;
- 一个方法中若使用多次 synchronized 域,可以考虑直接给方法加锁;
1.5) Volatile
volatile 关键字是在 1.5 后出现的
- 保证线程可见性
- MESI
- 禁止指令重排序
Synchronized 只能保证原子性,所以 DCL单例模式(Double check lock) 的写法,若不给实例加 volatile,在初始化过程中还是会引发线程安全问题。
一个 if 没加锁,且条件变量无 volatile 修饰,会由于指令重排序印发线程安全问题。