如何理解Java中眼花缭乱的各种并发锁?( 二 )


如何理解Java中眼花缭乱的各种并发锁?

文章插图
在 JDK 中定义了一个读写锁的接口ReadWriteLock,如源码所示:
public interface ReadWriteLock {/* 获取读锁 */Lock readLock();/* 获取写锁 */Lock writeLock();}ReentrantReadWriteLock 实现了ReadWriteLock接口,ReentrantReadWriteLock 支持锁降级不支持锁升级,可以由写锁降为读锁 。
3)多个线程竞争时是否要排队的情况多个线程竞争排队获取锁的情况,使用公平锁,如果,使用非公平锁 。所谓公平锁是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买,后来的人在队尾排着,这是公平的 。
在 Java 中可以通过构造函数初始化公平锁,如代码所示:
/*** 创建一个可重入锁,* true 表示公平锁,* false 表示非公平锁 。* 默认非公平锁 */Lock lock = new ReentrantLock(true);非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者某个线程一直得不到锁的饥饿状态 。
如何理解Java中眼花缭乱的各种并发锁?

文章插图
在 Java 中 synchronized 关键字是非公平锁,ReentrantLock默认也是非公平锁,如代码所示:
/*** 创建一个可重入锁,true 表示公平锁,false 表示非公平锁 。默认非公平锁*/Lock lock = new ReentrantLock(false);4)一个线程中的多个流程,是否获取同一把锁的情况如果一个线程中的多个流程能获取同一把锁,就使用可重入锁,如果线程的多个流程不能获取通一把锁,就是用不可重入锁 。可重入锁又称为递归锁,是指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁 。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁 。对于synchronized而言,也是一个可重入锁 。可重入锁的一个好处是可一定程度避免死锁 。以 synchronized 为例,来看这样一段代码:
public synchronized void mehtodA() throws Exception{// Do some magic tingsmehtodB();}public synchronized void mehtodB() throws Exception{// Do some magic tings}在这段代码中 methodA() 调用 methodB(),如果一个线程调用methodA() 已经获取了锁再去调用 methodB() 就不需要再次获取锁了,这就是可重入锁的特性 。如果是不可重入锁的话,mehtodB() 可能不会被当前线程执行,可能造成死锁 。
5)某个线程锁住同步资源失败,是否不阻塞的情况如果某个线程锁住同步资源失败,但是希望这个线程不阻塞,就可以使用自旋锁或者自适应自旋锁 。自旋锁是指线程在没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环就是所谓的自旋 。
如何理解Java中眼花缭乱的各种并发锁?

文章插图
自旋锁的目的是为了减少线程被挂起的几率,因为线程的挂起和唤醒也都是耗资源的操作 。如果锁被另一个线程占用的时间比较长,即使自旋了之后当前线程还是会被挂起,忙循环就会变成浪费系统资源的操作,反而降低了整体性能 。因此自旋锁是不适应锁占用时间长的并发情况的 。在 Java 中,AtomicInteger 类就有自旋的操作,来看这样一段代码:
public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset);} while (!compareAndSwapInt(o, offset, v, v + delta));return v;}循环条件调用compareAndSwapInt()方法,被称为CAS操作,如果失败就会一直循环获取当前 value 值然后重试,这个过程叫自旋 。在JDK1.6引入了自适应自旋,这个就比较智能了,自旋时间不再固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定 。如果虚拟机认为这次自旋也很有可能再次成功那就会次序较多的时间,如果自旋很少成功,那以后可能就直接省略掉自旋过程,避免浪费处理器资源 。

经验总结扩展阅读