什么是重入锁?

锁主要用来控制多线程访问的行为,对于同一个线程,如果连续两次对同一把锁进行lock,那么这个线程会被卡死在那里,这样的特性很不好,在实际的开发中,方法之间的调用方式错综复杂,如果不小心可能在多个不同的方法中,反复调用 lock(),这样就会把自己卡死。

所以,重入锁就是用来解决这个问题的,重入锁使同一个线程可以对同一把锁在不释放的前提下,反复的加锁不会导致线程的卡死,唯一的一点就是需要保证 unlock() 的次数和 lock()一样的多。

重入锁的实现

Java中的锁都来自与 Lock 接口,而 ReadWriteLoc k实现的 lock 接口,本文主要分析这两个接口的几个子类的实现细节。

而重入锁最重要的方法就是lock()。

  • lock():加锁,如果锁已经被别人占用了,就无限等待
  • tryLock(long timeout, TimeUnit unit) throws InterruptedException:尝试获取锁,等待timeout时间。同时,可以响应中断
  • unlock() :释放锁
  • tryLock():不会进行任何等待,如果能够获得锁,直接返回true,如果获取失败,就返回false
  • lockInterruptibly():可以响应中断,lock方法会阻塞线程直到获取到锁
  • newCondition():返回一个条件变量,一个条件变量也可以做线程间通信来同步线程。

重入锁实现原理

重入锁实现的主要类如下图:


重入锁的核心功能委托给内部类 Sync 实现,并且根据是否是公平锁有 FairSync 和 NonfairSync 两种实现。这是一种典型的策略模式。

实现重入锁的方法很简单,就是基于一个状态变量 state。这个变量保存在AbstractQueuedSynchronizer对象中

private volatile int state;

当 stat e== 0 时,表示锁是空闲的,大于零表示锁已经被占用, 它的数值表示当前线程重复占用这个锁的次数。因此,lock() 的最简单的实现是:

final void lock() {// CAS设置共享状态,返回true表示成功获取共享状态if (compareAndSetState(0, 1))// 设置当前线程为共享状态的持有线程setExclusiveOwnerThread(Thread.currentThread());else// 否则调用AQS中的acquire(int arg)尝试获取同步状态,失败则加入等待队列,自旋获取共享状态acquire(1);}

下面是acquire() 的实现:

 public final void acquire(int arg) {//tryAcquire() 再次尝试获取锁,//如果发现锁就是当前线程占用的,则更新state,表示重复占用的次数,//同时宣布获得所成功,这正是重入的关键所在if (!tryAcquire(arg) &&// 如果获取失败,那么就在这里入队等待acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果在等待过程中 被中断了,那么重新把中断标志位设置上selfInterrupt();
}

下面我们说一下公平锁 和 非公平锁

公平的重入锁

初始化时, state = 0,表示无人抢占了锁。这时候,这时线程 A 请求锁,获得了锁,把 state + 1,如下所示:

线程 A 取得了锁,把 state +1,这时候 state 改为 1,线程 A 继续执行其他任务,此时线程B请求锁,线程 B 无法获取锁,生成节点进行排队,如下图所示:


初始化的时候,会生成一个空的头节点,然后才是线程 B 节点,这时候,如果线程 A 又请求锁,是否需要排队?答案当然是否定的,否则就直接死锁了。当 A 再次请求锁,这时候的状态如下图所示:


到了这里,相信大家应该明白了什么是可重入锁了吧。就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。如果线程 A 释放了一次锁,如下图所示:


仅仅是把状态值减了,只有线程 A 把此锁全部释放了,状态值减到 0 了,其他线程才有机会获取锁。当线程 A 把锁完全释放后,state 恢复为 0,然后会通知队列唤醒线程 B 节点,使B可以再次竞争锁。当然,如果线程 B 后面还有线程 C,线程 C 继续休眠,除非 B 执行完了,通知了线程 C。注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。

非公平的重入锁

理解了公平锁的话,那非公平锁就容易理解了,当线程 A 执行完之后,要唤醒线程 B 是需要时间的,而且线程 B 醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程 C,那么线程 C 是有可能获取到锁的,如果线程 C 获取到了锁,线程 B 就只能继续等待休眠了。

那公平锁和非公平锁实现的核心区别在哪里呢?如下所示:

//非公平锁 final void lock() {//直接抢了再说if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else//抢不到,就进队列慢慢等着acquire(1);}//公平锁final void lock() {//直接进队列等着acquire(1);}

我们从代码中可以看到,非公平锁如果第一次争抢失败,后面的处理和公平锁是一样的,都是进入等待队列慢慢等。

而对应tryLock()方法也非常类似的:

 //非公平锁
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 如果当前共享状态未被其他线程占用if (c == 0) {// 尝试通过CAS占有当前共享状态if (compareAndSetState(0, acquires)) {// 设置共享状态持有线程为当前线程setExclusiveOwnerThread(current);return true;}}// 如果共享状态已被占用,则判断当前占用共享状态的线程是否就是当前线程else if (current == getExclusiveOwnerThread()) {// 如果是则自增获取次数,设值stateint nextc = c + acquires;if (nextc < 0) throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}//非公平锁
protected final boolean tryAcquire(int acquires) {// 获取到当前线程final Thread current = Thread.currentThread();// 获取当前同步状态int c = getState();// 如果同步状态为0,则说明当前同步状态已完全释放if (c == 0) {// 1、hasQueuedPredecessors判断当前节点是否存在前驱节点// 2、如果不存在则CAS设置state的值if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {// 前两个都满足则,设置同步状态持有的线程为当前线程setExclusiveOwnerThread(current);return true;}}// 否则判断当前线程和持有共享状态的线程是否是同一个线程else if (current == getExclusiveOwnerThread()) {// 如果是,重入,状态值增加int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");// 设值新的状态值setState(nextc);return true;}return false;}

引入 Condition

Condition 的作用与 Object.wait() 和 Object.notify() 的作用大致是相同的。但是 wait() 和 notify() 方法是与synchronized 关键字合作使用的,而 Condition 是与重入锁相关联的。通过 Lock 接口(重入锁实现了这一接口)的new Condition() 方法可以生成一个与当前重入锁绑定的 Condition 实例。利用 Condition 对象,可以让线程在合适的时间等待,或者在某一个特定的时间得到通知。

Condition 接口提供的方法

/*** 使当前线程进入等待状态直到被通知(signal)或中断* 当其他线程调用 singal() 或 singalAll() 方法时,该线程将被唤醒* 当其他线程调用 interrupt() 方法中断当前线程* await() 相当于 synchronized 等待唤醒机制中的 wait() 方法*/void await() throws InterruptedException;
//当前线程进入等待状态,直到被唤醒,该方法不响应中断要求
void awaitUninterrruptibly();
 //调用该方法,当前线程进入等待状态,直到被唤醒或被中断或超时//其中 nanosTimeout 指的等待超时时间,单位纳秒
long awaitNanos(long nanosTimeout) throws InterruptedException;
//同 awaitNanos,但可以指明时间单位
boolean await(long time, TmeUnit unit) throws InterruptedException;
 //调用该方法当前线程进入等待状态,直到被唤醒、中断或到达某个时//间期限(deadline),如果没到指定时间就被唤醒,返回 true,其他情况返回 false
boolean await(Date deadline) throws InterruptedException;
//唤醒一个等待在 Condition 上的线程,该线程从等待方法返回前必须
//获取与 Condition 相关联的锁,功能与notify()相同
void signal();
//唤醒所有等待在 Condition 上的线程,该线程从等待方法返回前必须
//获取与 Condition 相关联的锁,功能与 notifyAll() 相同
void signalAll();

代码演示一下 Condition 的使用:

public class ReentrantLockCondition implements Runnable{public static ReentrantLock lock = new ReentrantLock();//通过 ReentrantLock 创建 Condition 实例,并与之关联public static Condition  condition = lock.newCondition();@Overridepublic void run(){try{lock.lock();condition.await();System.out.println("Thread is going on");}catch (InterruptedException e){e.printStackTrace();}finally{lock.unlock();}}public static void main(String[] args) throws InterruptedException {// TODO Auto-generated method stubReentrantLockCondition condition1 = new ReentrantLockCondition();Thread thread= new Thread(condition1 );thread.start();Thread.sleep(2000);lock.lock();condition.signal();lock.unlock();}}

与 Object.wait() 和 Object.notify() 方法类似,当前线程使用 Condition.await() 时,要求线程持有相关的重入锁,在Condition.await() 调用后,这个线程会释放这把锁。同理,在 Condition.signal() 方法调用时,也要求线程先获得相关的锁。在 signal() 方法调用后,系统会从当前 Condtion 对象的等待队列中,唤醒一个线程。一旦线程被唤醒,它会重新尝试获得与之绑定的重入锁,一旦成功获取,就可以继续执行了。因此,在 signal() 方法调用之后,一般需要释放相关的锁,谦让给被唤醒的线程,让它可以继续执行。

总结

对于重入锁,这里我们需要知道几点:

  • 对于同一个线程,重入锁允许你反复获得通一把锁,但是,申请和释放锁的次数必须一致
  • 默认情况下,重入锁是非公平的,公平的重入锁性能差于非公平锁
  • 重入锁的内部实现是基于 CAS 操作的
  • 重入锁的伴生对象 Condition 提供了 await() 和 singal() 的功能,可以用于线程间消息通信

详述重入锁-ReentrantLock相关推荐

  1. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  3. java中的账户冻结原理_java可重入锁(ReentrantLock)的实现原理

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  4. java多线程---重入锁ReentrantLock

    1.定义 重入锁ReentrantLock,支持重入的锁,表示一个线程对资源的重复加锁. 2.底层实现 每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获 ...

  5. 重入锁ReentrantLock详解

    重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.除此之外,该锁的还支持获取锁时的公平和非公平性选择. 在AQS实现中,当一个线程调用Mute ...

  6. Java多线程系列——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为"独占锁". ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychr ...

  7. java lock可重入_Java源码解析之可重入锁ReentrantLock

    本文基于jdk1.8进行分析. ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock. 首先看一下源码中对ReentrantLock的介绍. ...

  8. Java多线程——重入锁ReentrantLock源码阅读

    上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...

  9. Java 并发编程之可重入锁 ReentrantLock

    Java 提供了另外一个可重入锁,ReentrantLock,位于 java.util.concurrent.locks 包下,可以替代 synchronized,并且提供了比 synchronize ...

最新文章

  1. Java面试题及答案整理(2022年140道)持续更新
  2. 【Java】面试高频考题---topK问题详解(堆heap求解)
  3. MySQL Connector/ODBC 5.2.2 发布
  4. 弱口令上传shell_emlog后台拿shell
  5. python opengl 截图_初试PyOpenGL二 (Python+OpenGL)基本地形生成与高度检测
  6. 25 个在 Web 中嵌入图表的免费资源
  7. AspNetCoreMassTransit Courier实现分布式事务
  8. skywalking使用方法_skywalking 6.2配置相关和使用
  9. 性能指标之速率、带宽、吞吐量
  10. Kaggle电影数据集:movies_metadata.csv
  11. 毕业论文封面LaTeX模板
  12. 面试官问你“有什么问题问我吗?”,你该如何回答? 1
  13. 测试用例以及相关问题
  14. 前端开发工程师必备网站
  15. Cannot get a STRING value from a NUMERIC cell
  16. 小程序获取当前进页面的来源
  17. 胆囊炎的临床症状有哪些?
  18. 从备份升级到容灾,利用华为云就可以做到的灾备方案
  19. 华为nova8计算机功能在,华为nova8隐藏功能怎么开启(华为nova8的隐藏功能)
  20. 微软 Win 10X 系统非常震撼

热门文章

  1. Virtual reality
  2. java bnf_Java语言标准BNF
  3. mysqlworkbench导入excel数据屡次失败???你可能只是某个地方又智障了。没错,新手总是这么容易被智障问题绊住。(尝试3个小时总算成功了)
  4. 华为AC+AP设备上线入门,中小网络AP上线配置思路入门
  5. 百度有钱联盟邀请码有效期48小时
  6. JNI官方规范中文版
  7. 20岁就业难、30岁被裁员、35岁瓶颈期…打工人真的没有退路了吗
  8. 科大讯飞AIUI-配送技能
  9. DZ论坛页面增加摘要
  10. CSS3渐变(Gradients)