深入理解Java虚拟机学习笔记(九)

线程安全与锁优化

线程安全

线程安全的定义

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

Java语言中的线程安全

  • 不可变:不可变的对象只要被正确构建出来(没有发生this引用逃逸的情况),那其外部的可见状态永远不会改变。例如:String,java.lang.Integer等。
  • 绝对线程安全:完全满足线程安全的定义。注意:Java API中大多数标注自己是线程安全的类,都不是绝对线程安全的!
  • 相对线程安全:保证对于对象单独的操作是线程安全的,但是对于一些特定顺序的连续调用,需要在调用端使用同步手段来保证调用的正确性。例如同时对一个Vector进行remove(),get()和size()操作,在使用get()时,之前在size()范围内的序号可能已经失效了。Java中大部分线程安全类都属于这种类型。
  • 线程兼容:对象本身不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。
  • 线程队里:无论是否采取了同步手段,都无法在多线程环境中并发使用的代码。如Thread.suspend()和Thread.resume()等。

线程安全的实现方法

  1. 互斥同步(悲观锁):只有一个线程能持有锁,其他线程在其释放锁之前只能挂起。如synchronized关键字和ReentrantLock。
  2. 非阻塞同步(乐观锁):不加锁,基于冲突检测的并发策略,假设操作没有冲突,如果有冲突就重试,直到成功为止。典型的方法是CAS操作,Java中JUC包中的原子数字类就是使用了CAS操作。
  3. 无同步方案:天生线程安全。
    • 可重入代码:可以在代码执行的任意时刻中断它,在控制权返回后,原来的程序不会出现任何错误。
    • 线程本地存储:将共享数据的代码限制在同一线程中执行。

      同步:指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用。

锁优化

  1. 自旋锁:线程在申请锁时,如果锁已被其他线程持有,线程不会被挂起等待恢复,而是不断循环等待锁被释放。
  2. 锁消除:JIT会将一些代码上要求同步,但是检测到不可能存在共享数据竞争的锁进行消除。
  3. 锁粗化:在相近的代码或循环体中对同一对象反复加锁解锁时,虚拟机将会把加锁同步的返回扩展到整个操作序列的外部。
  4. 轻量级锁:在同步周期中不存在竞争时,使用CAS操作将对象的Mark Word更新为指向Lock Record(Mark Word的拷贝)的指针,以此代替互斥量;当同步周期中出现竞争时,轻量级锁失效,膨胀为重量级锁。
  5. 偏向锁:当锁第一次被一个线程持有时,将线程ID通过CAS操作写入Mark Word,此后该线程进入同步块将不再需要进行同步操作;若有另一线程尝试获取锁,则撤销偏向恢复到未锁定或轻量级锁的状态。