通过源码看Java中ReentrantLock类

ReentrantLock,独占式锁,支持一个线程对资源的重复加锁,再次加锁时不会阻塞自己(synchronized关键字隐式地支持锁重入)。此外,它还支持获取锁时选择公平或非公平模式。

那么,何为公平锁?指在绝对时间上,先申请锁的请求一定先被满足,那么这个锁是公平的;反之,是不公平的。可见,获取公平锁时,按照FIFO原则,在同步队列中等待时间最长的线程最先获取锁。

1 创建ReentrantLock

无参构造器默认使用非公平锁。入参为true时使用公平锁。 

 

2 同步器实现

在ReentrantLock中,内部类Sync继承AbstractQueuedSynchronizer,作为同步组件。它有两个子类:NonfairSync、FairSync,分别是非公平锁、公平锁的实现。 

 

3 加锁

分别来看公平、非公平两种模式:

  • 非公平模式时,lock()时先CAS尝试获取锁,如果成功则退出,不用进入同步队列等待。 
  • 公平模式时,lock()时直接走AQS的acquire(),如果tryAcquire(arg)失败,线程将进入同步队列等待。 

4 锁重入

4.1 加锁

锁重入的实现在ReentrantLock.Sync#nonfairTryAcquire方法中,关键在于:当同步状态不可获取时,锁需要能够识别当前线程是否是锁持有者。 

整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

 锁对象如何记录锁持有者呢?AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer类,后者的exclusiveOwnerThread属性,能够记录持有独占式锁的线程。 

 当线程重复获得锁时,对state计数自增即可。

4.2 释放锁

tryRelease(int releases)方法中:

  • 当线程对锁重入了n次后,前n-1次释放锁时,state计数自减,返回false,并未真正失去锁;
  • 第n次释放锁时,计数等于0,方法返回true,才会唤醒阻塞中的后继线程。  

5 公平与非公平

区别在于:

  • 公平模式时,申请资源的(多个)新线程到来时,即使同步状态可被获取,如果队列中有等候的线程,当前线程也不能尝试去获取,必须将添加到队尾,排队等候。 
  • 非公平模式时,(多个)新线程申请资源时,可以尝试争抢资源;成功时不用入队,失败时才被添加到队尾排队。 

使用非公平锁时,如果一直有新线程到来,可能导致入队很早的线程,很久不能获取到锁,造成饥饿。但是,它能够减小线程切换次数(新线程有很大概率不用入队阻塞),因而非公平锁有更大的吞吐量

公平锁必须按照FIFO原则,依次排队来获取锁。因此,公平锁往往没有非公平锁的效率高。但是,它可以减少饥饿发生的概率,等待越久的线程越是优先获取到锁。

6 使用ReentrantLock来避免死锁

常常有这样的场景:一个线程任务需要同时获得多把锁时,才能顺利执行。

使用synchronized时,很容易造成死锁。如下代码,t1线程在获得lock1后,阻塞在获取lock2;正好有其他线程获取了lock2,阻塞在获取lock1。此时,发生了死锁。

 

java

代码解读

复制代码

Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { log.debug("lock1"); sleep(1); synchronized (lock2) { log.debug("lock2"); // do something } } }, "t1");

如果线程获取更多锁失败时,能够自动释放已经获得的锁,将避免可能发生的死锁问题。

ReentrantLock的tryLock()方法是非阻塞的,使用它很容易实现这个功能。

 

java

代码解读

复制代码

ReentrantLock lock1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Thread t1 = new Thread(() -> { while (true) { if (lock1.tryLock()) { log.info("t1 get lock1"); try { // tryLock是非阻塞的 if (lock2.tryLock()) { log.info("t1 get lock2"); try { log.info("t1 do something..."); break; } catch (Exception e) { } finally { lock2.unlock(); } } } catch (Exception e) { } finally { lock1.unlock(); } } } }, "t1");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值