可重入锁,也叫作递归锁,指的是同一线程得到锁以后,又去得到同一把锁,若是可以成功,就是可重入锁。若是不举例,这个概念可能会有点抽象。当一个线程执行到某个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,可重入锁,是一种能够彻底替代synchronized的递归无阻塞同步机制。在JDK5的早期版本中,可重入锁的性能远比synchronized好,从JDK6开始synchronized进行了大量的优化,使得二者性能相差不大。可是,ReentrantLock提供了比synchronized更强大、更灵活的锁机制。JDK源码中能够看到大量ReentrantLock的使用。
咱们经过源码来看下ReentrantLock是怎么实现可重入的,ReentrantLock的lock()方法用来加锁。
lock方法首先调用compareAndSetState()方法判断锁是否被线程占有,若是没有被线程占有,经过setExclusiveOwnerThread()方法记录当前线程为占有锁的线程,以便后续进行可重入判断。若是锁被线程占有的,调用acquire()申请锁。acquire()中最重要的一个方法是nonfairTryAcquire(),这个方法用来判断线程最终是否可以得到锁。代码以下:
一行行来分析下这段代码:多线程
再看下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