锁优化的若干手段

文章目录
  1. 1. 代码层面
    1. 1.1. 避免死锁
    2. 1.2. 减小锁的持有时间
    3. 1.3. 减小锁粒度
    4. 1.4. 实现锁分离
    5. 1.5. 重入锁和内部锁
    6. 1.6. 锁粗化
    7. 1.7. 使用原子操作类
  2. 2. JVM层面
    1. 2.1. 自旋锁(Spinning Lock)
    2. 2.2. 锁消除(Lock Elimination)
    3. 2.3. 锁偏向(Biased Lock)

代码层面

避免死锁

死锁出现的四要素:

  • 互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放;
  • 请求与保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放
  • 不可中条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
  • 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

减小锁的持有时间

减小锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力。
举例:synchronized 同步方法块,而不是整个方法。

减小锁粒度

所谓的减少锁粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力。

举例:ConcurentHashmap 中使用分段锁提高 put() 操作的并发能力,默认情况下 ConcurentHashmap 有16个段,理想情况下,它可以同时接受16个线程同时插入。

减小锁引入的问题:当需要获取全局锁时,其消耗的资源会比较多。如 ConcurentHashmap 中的 size() 方法需要同时获取所有段的锁方能顺利实施(当然首先会使用无锁方式获取,失败时采用加锁方式获取)。

实现锁分离

根据读写操作功能的不同,进行有效的锁分离。如分别使用读锁和写锁,读锁之间是相容的,即对象可以持有多个读锁;对象对写锁的占用是独占式的,只有在对象没有锁的情况下才能获取对象的写锁。

举例LinkedBlockingQueue
LinkedBlockingQueue 的实现中,*take()put()*函数分别从队列中取得数据和往队列中增加数据,JDK使用了两把不同的锁分离了这个两个操作,使得两者在真正意义上成为可并发的操作。

重入锁和内部锁

重入锁比内部锁功能更强大,但内部锁使用更简单。JDK1.7后两者之间的性能已经差不了太多了,且内部锁将会是JDK以后优化的重点,所以建议优先使用内部锁。

锁粗化

虚拟机在遇到一连串连续对同一锁不断请求和释放的操作时,会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步数,这个操作叫做锁的粗化。

1
2
3
4
5
6
7
8
9
public void demo(){
synchronized(lock){
//do something
}
//其他非同步操作
synchronized(lock){
//do something
}
}

锁粗化后:

1
2
3
4
5
6
public void demo(){
synchronized(lock){
//do something
//其他非同步操作
}
}

又如:循环中的加锁操作(循环体执行很快),应对整个循环加锁,可降低锁的请求。

使用原子操作类

使用java.util.concurrent.atomic下的原子操作类,替换有锁的操作,原子操作类的基础是CAS(Compare And Swap),该操作比基于锁的方式拥有更优越的性能,大部分的线程处理器都已经支持原子化的CAS指令。

JVM层面

自旋锁(Spinning Lock)

锁的等待只需要很短的时间,这段时间可能比线程挂起并恢复的时间还要短,因此JVM引入了自旋锁。

自旋锁可以使线程没有取得锁时,不被挂起,而转去执行一个空循环,在若干个空循环后,线程如果获得了锁,则继续执行,若线程依然没有获得锁,才会被挂起,避免用户线程和内核的切换的消耗

在JVM中使用 -XX:UseSpinning 参数来开启自旋锁,使用 -XX:PreBlockSpin 来设置自旋锁的等待次数。

锁消除(Lock Elimination)

通过对上下文的扫描,去除不可能存在共享资源竞争的锁,节省毫无意义的请求锁时间。

举例:StringBuffer、Vector这些同步类用于了没有多线程竞争的场合。

锁偏向(Biased Lock)

如果程序没有竞争,则取消之前已经获取锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁,无需在进行相关的同步操作,如果在此之间有其他线程进行了锁请求,则锁退出偏向模式。

偏向锁在锁竞争激烈的场合没有优化效果,因为大量的竞争会导致持有锁的进程不停地切换,锁也很难一直保持在偏向模式,使用 -XX:-UseBiasedLocking=false 禁用偏向锁。