Java可重入锁解疑

什么是可重入锁

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

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类,这个类就叫可重入锁,我们可以从源码角度去探索下这个类是怎么实现可重入的。

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等。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值