Java虚拟机札记-线程安全与锁优化

时间:2022-12-27 18:45:48

线程安全

什么是线程安全

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

线程安全级别
  • 不可变
  • 绝对线程安全
  • 相对线程安全
  • 线程兼容
  • 线程对立

不可变
不可变的的数据一定是线程安全的。Java语言中,如果共享数据是基本数据类型,定义时使用final修饰就可以保证它是线程安全的。如果共享数据是一个对象,需要保证对象的行为不会对其状态产生影响。最简单的方法就是讲对象中带有状态的变量都声明为final。Java API中符合不可变条件的类型,有String,枚举,Number的部分子类,如Long、Double、BigInterger、BigDecimal,同为Number子类的AtomicInterger和AtomicLong则不是不可变的。

绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。这个要求是十分严格的。Java API中标明是线程安全的类都达不到这个要求,比如Vector。

相对线程安全
相对线程安全是指我们通常所说的线程安全。单线程环境下不需要做任何的额外措施。多线程环境下需要使用额外的同步手段来保证线程安全。Java API中标明是线程安全的类大都是这种类型,比如Vector,Hashtable。

线程兼容
线程兼容是指对象本身不是线程安全的,但可以通过同步手段来保证对象在并发环境中可以安全地使用。Java API中大部分的类都是这种类型,比如ArrayList,HashMap。

线程对立
指即使采用了同步手段,依然无法在并发环境中安全使用的代码。比如Thread.suspend()和resume()方法。

线程安全实现方法

请参考Java并发编程札记-(一)基础-05线程安全问题-线程安全问题解决方法

锁优化

锁优化内容

高效并发是JDK1.5到JDK1.6的一个重要改进,HotSpot虚拟机开发团队在这个版本上实现了各种锁优化技术,比如

  • 自旋锁和适应性自旋
  • 锁消除
  • 锁粗化
  • 轻量级锁
  • 偏向锁
自旋锁

为什么需要自旋锁?
互斥同步对性能最大的影响是阻塞,挂起线程和恢复线程的操作都需要转入内核态中完成,这给系统的并发性带来很大压力。同时很多应用共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。

自旋锁原理
为了解决上述问题,先不挂起线程,让它等一会儿,说不定持有锁的线程很快就会释放锁了。为了让线程等待,我们只需要让线程执行一个自旋循环。这就是自旋锁。

Java中的自旋锁

while(!object1 .compareAndSet(null, object2)){
。。。
}

上面的while循环在object1为null,且成功被设为object2前一直自旋。

自旋锁的缺点?
自旋等待本身虽然避免了线程切换的开销,但它要占用处理器时间。所以如果锁被占用的时间很短,自旋等待的效果就非常好,但如果时间很长,那么自旋的线程只会白白消耗处理器的资源。所以自旋等待的时间要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,那就应该使用传统的方式挂起线程了。自旋次数的默认值为10,可以通过参数-XX:PreBlockSpin来更改。

适应性自旋
JDK1.6中引入了自适应的自旋锁。自旋的时间不固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果一个锁对象,自旋等待刚刚成功获得锁,并且持有锁的线程正在运行,那么虚拟机认为这次自旋仍然可能成功,进而运行自旋等待更长的时间。如果对于某个锁,自旋很少成功,那在以后要获取这个锁,可能省略掉自旋过程,以免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机也会越来越聪明。

锁消除

锁消除是指虚拟机即时编译器在运行时对一些代码上要求同步,但被检测到不可能存在共享数据竞争,这是要对锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持。程序员怎么会在明知道不存在数据竞争的情况下使用同步呢?答案是很多同步措施不是程序员自己加入的。

锁粗化

原则上,同步块的作用范围要尽量小。这样做是为了使需要同步的操作数量尽可能变小。大部分情况下,上面的原则都是正确的,但如果一系列的连续操作都对同一个对象反复加锁和解锁,这样即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。锁粗化就是增大同步块的作用域。

轻量级锁

在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

偏量级锁

消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。在无竞争的情况下,把整个同步都消除掉。这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。

本文就讲到这里,本文已收录于Java并发编程札记专栏

本文内容摘录或总结自《深入理解Java虚拟机 JVM高级特性与最佳实践》。