本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。

1) 进程与线程

进程:Program app,如:qq.sh
线程:即一个进程里,最小的执行单元;进程中不同的执行路径

1.1) 启动线程方式

严谨来说只有两种方式,第三种线程池实际上也是用的 Thread 和 Runnable

  1. Thread
  2. Runnable
  3. new ThreadPoolExecutor

1.2) 线程状态

可以通过 target.getState 获取线程状态

  • New
  • Runnable
    • Ready
    • Running
    • TimedWaiting
    • Waiting
    • Blocked
    • Teminatted
  • Terminated

1.3) 线程基础 API

  1. sleep
  2. yield(): 将当前线程丢回等待队列
  3. target.join: join 到 target 线程中,target 执行后继续执行当前线程
  4. stop: 建议让线程正常结束,不建议使用 stop,容易产生线程的不一致。
  5. target.Interrupt: 向 target 线程发送 interrupt 信号
    • target 处于非阻塞状态时:target 中可以根据 Thread.currentThread().isInterrupted() 来捕获 interrupt 信号
    • target 处于阻塞状态时:target 可以通过捕获 InterruptedException 异常来处理 Interrupt 信号

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 开始,引入了锁升级的概念:

  1. 偏向锁
    如果锁对象只被某个线程访问,默认不加锁,只在 MarkWord 中记录该线程号,即偏向锁
  2. 自旋锁
    若有其他线程访问这把锁,升级为自旋锁
  3. 重量级锁
    • 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 修饰,会由于指令重排序印发线程安全问题。