Java可重入锁解疑

什么是可重入锁

可重入锁,也叫作递归锁,指的是同一线程得到锁以后,又去得到同一把锁,若是可以成功,就是可重入锁。若是不举例,这个概念可能会有点抽象。当一个线程执行到某个synchronized方法时,好比说method1,而在method1中会调用另一个synchronized方法method2,此时线程没必要从新去申请锁,而是能够直接执行方法method2。
看下这段代码:java

public class Demo {
    public synchronized void method1() {
        method2();
    }

    private synchronized void method2() {

    }
}

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而因为method2也是synchronized方法,假如synchronized不具有可重入性,此时线程A须要从新申请锁。可是这就会形成一个问题,由于线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁,致使死锁发生。
幸亏synchronized是可重入的,因此不会由于这种状况发生死锁。至于synchronized可重入是怎么实现的,涉及到JVM实现,暂时不知道,之后会去了解。可是,熟悉Java并发包的同窗应该知道ReentrantLock类,这个类就叫可重入锁,咱们能够从源码角度去探索下这个类是怎么实现可重入的。web

ReentrantLock——可重入锁

ReentrantLock,可重入锁,是一种能够彻底替代synchronized的递归无阻塞同步机制。在JDK5的早期版本中,可重入锁的性能远比synchronized好,从JDK6开始synchronized进行了大量的优化,使得二者性能相差不大。可是,ReentrantLock提供了比synchronized更强大、更灵活的锁机制。JDK源码中能够看到大量ReentrantLock的使用。
咱们经过源码来看下ReentrantLock是怎么实现可重入的,ReentrantLock的lock()方法用来加锁。
这里写图片描述
lock方法首先调用compareAndSetState()方法判断锁是否被线程占有,若是没有被线程占有,经过setExclusiveOwnerThread()方法记录当前线程为占有锁的线程,以便后续进行可重入判断。若是锁被线程占有的,调用acquire()申请锁。acquire()中最重要的一个方法是nonfairTryAcquire(),这个方法用来判断线程最终是否可以得到锁。代码以下:
这里写图片描述
一行行来分析下这段代码:多线程

  • 第一行,获取当前线程,用来跟以前lock()方法中调用的setExclusiveOwnerThread()的线程对比。
  • 第二行,调用getState()方法,这个state变量,就是用来记录锁是否被占有和锁被占有的次数,0表示没有线程占有,1表示被线程占有,2表示被同一个线程占有两次,须要释放锁两次,以此类推。
  • 第三行,根据state的值判断锁是否被占有。
  • 第四行,若是没有占有,调用compareAndSetState()方法上锁。这compareAndSetState()方法也很重要,ReentrantLock锁机制就靠它实现的。
  • 第五行,记录当前占有锁的线程信息。
  • 第九行,这是实现可重入的关键代码,在第三行判断锁已经被占有后,若是没有第九行这段代码判断,那么同一线程就没法获取该锁,从而致使死锁的发生。
  • 接下来几行代码就是记录线程占有锁的次数,并设置到state变量上。

再看下unlock()方法,看看可重入锁是如何释放锁的,代码以下:
这里写图片描述
unlock()方法调用ReentrantLock的内部类Sync的release()方法:
这里写图片描述
tryRelease()方法作了最终的释放锁操做:
这里写图片描述
这段代码不难理解,先判断当前线程是不是持有锁的线程,是的话,state变量减去相应的数值,再判断锁是否彻底释放。如今去了解下更关键的compareAndSetState()方法,它是如何保证数据同步的:
这里写图片描述
只有一行代码,很明显又作了封装,不过此次使用Unsafe这个类的native方法。unsafe.compareAndSwapInt(this, stateOffset, expect, update); 这行代码实现的功能是原子性的判断this指向的类,stateOffset所表明的属性的值,是否等于expect值,若是等于就将该值更新为update值。这句话有点绕,须要仔细体会下。this表示当前类,这个是确定的,那么stateOffset所表明的是哪一个属性呢?下面这段代码证实,stateOffset表明的是state属性。
这里写图片描述并发

总结

synchronized和ReentrantLock都具备可重入性,就是为了不线程屡次访问同一个锁时,出现死锁的状况。可是,synchronized和ReentrantLock的代码实现是不一样的,synchronized是基于JVM层面实现的,ReentrantLock是基于底层CPU指令实现。尽管ReentrantLock的功能比synchronized更强大,但仍是强烈推荐在多线程应用程序中使用synchronized关键字,由于实现方便,后续工做由JVM来完成,可靠性高。只有在肯定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其余机制,如ReentrantLock等。svg