为了在线程之间更加高效地共享数据,解决竞争问题,从而提高效率。针对不同应用场景,提出了以下几种锁优化的思路。
自旋锁、自适应自旋
Java多线程中,互斥同步对性能影响最大的是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,这给系统的并发性带来了很大的影响。
很多时候,共享数据的锁定状态只会持续很短一段时间,为了这段时间去挂起、恢复线程并不值得。为了让线程等待(并不是被挂起),我们只需要让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
如果锁被占用的时间很短,用自旋锁自旋等待的效果就非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗资源,适得其反。
所以就有了 自适应自旋 ,根据程序运行和性能监控的信息,对自旋时间预测。
锁消除
锁消除是指JVM即时编译器在运行时,一些代码要求同步,但是被检测到不可能存在共享数据竞争,然后就将锁消除。
锁粗化
如果一系列连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体里面,那么即使没有线程竞争,频繁进行互斥同步操作也会导致不必要的性能损耗。因此就有了 锁粗化,增大锁的范围,减少加锁释放锁的次数。
乐观锁
synchronized、ReentrantLock这样的互斥同步属于悲观并发控制。其涉及到用户态到内核态转换、维护锁计数器、检查是否有被阻塞的线程需要唤醒等操作,性能开销较大。
基于冲突检测的乐观并发控制。就是先进行操作,如果没有其他线程竞争共享数据,就成功了;否则失败,重试到成功为止。这种乐观并发控制的许多实现都不需要把线程挂起,因此这种同步操作称为 非阻塞同步。
乐观并发控制,需要 操作 和 冲突检测 这两个步骤具备 原子性。
CAS指令(compare and set):比较并赋值。CAS指令执行时,当且仅当变量V符合旧预期值A时,CPU才用新值B更新V的值,否则就不执行更新。CAS指令的全部操作是一个原子操作。在JDK 1.5之后,Java程序中才能使用CAS操作。
CAS操作应该算是 乐观并发控制的一种实现。
CAS操作的逻辑漏洞。并发包提供了一个带有标记的原子引用类,可以通过控制变量的版本来保证CAS的正确性。
乐观锁在数据竞争频繁的情况下,会产生大量失败重试,从而极大降低性能。
偏向锁
锁偏向是一种针对加锁操作的优化手段。它的核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无须再做任何同步操作。这样就节省了大量有关锁申请的操作,从而提高了程序性能。因此,对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一个线程请求相同的锁。而对于锁竞争比较激烈的场合,其效果不佳。因为在竞争激烈的场合,最有可能的情况是每次都是不同的线程来请求相同的锁。这样偏向模式会失效,因此还不如不启用偏向锁。