java ReentrantLock条件锁实现原理示例详解

目录
  • 引言
    • 条件锁的使用
    • ReentrantLock.newCondition()
    • Condition.await
    • Condition.signal

引言

在前两篇文章中,我们了解了ReentrantLock内部公平锁和非公平锁的实现原理,可以知道其底层基于AQS,使用双向链表实现,同时在线程间通信方式(2)中我们了解到ReentrantLock也是支持条件锁的,接下来我们来看下,其内部条件锁的实现原理。

条件锁的使用

 public static void main(String[] args) {
     ReentrantLock lock = new ReentrantLock();
     Condition condition = lock.newCondition();
     ExecutorService executorService = Executors.newCachedThreadPool();
     executorService.execute(new Runnable() {
         @Override
         public void run() {
             lock.lock();
             System.out.println(Thread.currentThread().getName()+" enter lock first");
             System.out.println(Thread.currentThread().getName()+" await start");
             try {
                 condition.await();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
             System.out.println(Thread.currentThread().getName()+" await end");
             lock.unlock();
         }
     });
     executorService.execute(new Runnable() {
         @Override
         public void run() {
             lock.lock();
             System.out.println(Thread.currentThread().getName()+" enter lock first");
             System.out.println(Thread.currentThread().getName()+" start sleep");
             try {
                 Thread.sleep(20000);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
             System.out.println(Thread.currentThread().getName()+" end sleep");
             System.out.println(Thread.currentThread().getName()+" signalAll condition");
             condition.signalAll();
             System.out.println(Thread.currentThread().getName()+"signal end");
             lock.unlock();
         }
     });
 }

如上代码所示,一般情况下我们通过

 Condition condition = lock.newCondition();

创建条件对象,使用condition.await();表示当前线程需要等待条件才能继续执行,当线程执行到此处时,会进入等待队列等待,直到有另一个线程通过condition.signalAll();condition.signal();唤醒,此时表明当前线程执行条件已具备,此时当前线程继续执行,上述代码中,当前线程会转入AQS的同步等待队列中,去等待抢占lock锁,其运行结果如下图所示:

条件锁一般适用于线程需要具备一定条件后才能正确执行的情况。

ReentrantLock.newCondition()

上文看到Condition的创建和基本用法,接下来我们来看下Condition的实现原理,跟踪ReentrantLock的执行代码如下所示:

 // ReentrantLock.java
 public Condition newCondition() {
     return sync.newCondition();
 }
 ​
 // ReentrantLock内部类Sync中
 final ConditionObject newCondition() {
     return new ConditionObject();
 }

可以看到newCondition最终返回了一个ConditionObject类的对象,ConditionObject类代码如下所示:

 // AQS中声明的ConditionObject
 public class ConditionObject implements Condition, java.io.Serializable {
     private static final long serialVersionUID = 1173984872572414699L;
     private transient Node firstWaiter;
     private transient Node lastWaiter;
     public ConditionObject() { }
     private Node addConditionWaiter() {
     }
     private void doSignal(Node first) {
       .....
     }
     private void doSignalAll(Node first) {
       .....
     }
     private void unlinkCancelledWaiters() {
       .....
     }

相信大家已经看出来了,很熟悉的Node链表有没有?其中firstWaiter指向链表首位,lastWaiter指向链表尾,在该链表内维护一个Node的双向链表,结合AQS中实现,我们可以猜测出,在condition.await的时候会以当前线程创建Node节点,随后以插入条件队列,随后当执行condition.signal/condition.signalAll时,唤醒在链表上的这些节点,具体实现是不是这样呢?我们继续看

Condition.await

ConditionObject实现的await方法如下所示:

 private Node addConditionWaiter() {
     Node t = lastWaiter;
     // If lastWaiter is cancelled, clean out.
     if (t != null && t.waitStatus != Node.CONDITION) {
         unlinkCancelledWaiters();
         t = lastWaiter;
     }
     Node node = new Node(Thread.currentThread(), Node.CONDITION);
     if (t == null)
         firstWaiter = node;
     else
         t.nextWaiter = node;
     lastWaiter = node;
     return node;
 }
 public final void await() throws InterruptedException {
     if (Thread.interrupted())
         throw new InterruptedException();
     // 以当前线程创建Node对象,并添加值队尾
     Node node = addConditionWaiter();
     int savedState = fullyRelease(node);
     int interruptMode = 0;
     // 通过LockSupport阻塞线程
     while (!isOnSyncQueue(node)) {
         LockSupport.park(this);
         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
             break;
     }
     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
         interruptMode = REINTERRUPT;
     if (node.nextWaiter != null) // clean up if cancelled
         unlinkCancelledWaiters();
     if (interruptMode != 0)
         reportInterruptAfterWait(interruptMode);
 }

Condition.signal

ConditionObject中的signal函数实现如下所示:

 public final void signal() {
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     Node first = firstWaiter;
     if (first != null)
         // 对队首节点唤醒
         doSignal(first);
 }
 private void doSignal(Node first) {
     do {
         // 重置firstWaiter并不断尝试唤醒首节点
         if ( (firstWaiter = first.nextWaiter) == null)
             lastWaiter = null;
         first.nextWaiter = null;
     } while (!transferForSignal(first) &&
              (first = firstWaiter) != null);
 }
 final boolean transferForSignal(Node node) {
     // 尝试更新节点的waitStatus
     if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
         return false;
     // 当前线程可以正常执行了,将该节点移入同步等待队列中,尝试获取锁
     Node p = enq(node);
     int ws = p.waitStatus;
     // 如果可以获取锁,则立即唤醒执行
     if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
         LockSupport.unpark(node.thread);
     return true;
 }

Condition.signalAll的逻辑与signal基本一致,区别在于是将在该条件上等待的所有节点均移入同步等待队列中。

以上就是java ReentrantLock条件锁实现原理示例详解的详细内容,更多关于java ReentrantLock条件锁的资料请关注我们其它相关文章!

(0)

相关推荐

  • ReentrantLock重入锁底层原理示例解析

    目录 J.U.C 简介 Lock Lock 简介 Lock 的实现 Lock 的类关系图 常用API ReentrantLock 重入锁 重入锁的设计目的 ReentrantLock 的使用案例 ReentrantReadWriteLock ReentrantLock 的实现原理 AQS 是什么 AQS 的两种功能 AQS 的内部实现 Node 的组成 释放锁以及添加线程对于队列的变化 ReentrantLock 的源码分析 ReentrantLock 的时序图 NofairSync.lock

  • java编程FinalReference与Finalizer原理示例详解

    之前写了一篇java编程Reference核心原理示例源码分析的文章,但由于篇幅和时间的原因没有给出FinalReference和Finalizer的分析.同时也没有说明为什么建议不要重写Object#finalize方法(实际上JDK9已经将Object#finalize方法标记为Deprecated).将文章转发到perfma社区后,社区便有同学提出一个有意思的问题?"Object#finalize如果在执行的时候当前对象又被重新赋值,那下次GC就不会再执行finalize方法了,这是为什么

  • Java使用FileInputStream流读取文件示例详解

    一.File流概念 JAVA中针对文件的读写操作设置了一系列的流,其中主要有FileInputStream,FileOutputStream,FileReader,FileWriter四种最为常用的流 二.FileInputStream 1)FileInputStream概念  FileInputStream流被称为文件字节输入流,意思指对文件数据以字节的形式进行读取操作如读取图片视频等 2)构造方法 2.1)通过打开与File类对象代表的实际文件的链接来创建FileInputStream流对象

  • Sentinel熔断规则原理示例详解分析

    目录 概述 熔断(降级)策略 慢调用比例 概念 测试 异常比例 概念 测试 异常数 概念 测试 概述 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一. 由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积. Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时.异常比例升高.异常数堆积) 对这个资源的调用进行限制,让请求快速失败从而避免影响到其它的资源而导致级联错误. 当资源被降级后,在接下来的降级时间窗口之内

  • java设计模式责任链模式原理案例详解

    目录 引言 责任链模式定义 类图 角色 核心 示例代码 1.对请求处理者的抽象 2.对请求处理者的抽象 3.责任链的创建 责任链实现请假案例 案例类图 可扩展性 纯与不纯的责任链模式 纯的责任链模式 不纯的责任链模式 责任链模式主要优点 职责链模式的主要缺点 适用场景 模拟实现Tomcat中的过滤器机制 运行过程如下 分析Tomcat 过滤器中的责任链模式 引言 以请假流程为例,一般公司普通员工的请假流程简化如下: 普通员工发起一个请假申请,当请假天数小于3天时只需要得到主管批准即可:当请假天数

  • Java包装类的缓存机制原理实例详解

    这篇文章主要介绍了Java包装类的缓存机制原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java 包装类的缓存机制,是在Java 5中引入的一个有助于节省内存.提高性能的功能,只有在自动装箱时有效 Integer包装类 举个栗子: Integer a = 127; Integer b = 127; System.out.println(a == b); 这段代码输出的结果为true 使用自动装箱将基本类型转为封装类对象这个过程其实

  • Java实现经典大富翁游戏的示例详解

    目录 前言 主要设计 功能截图 代码实现 总结 前言 大富翁,又名地产大亨.是一种多人策略图版游戏.参与者分得游戏金钱,凭运气(掷骰子)及交易策略,买地.建楼以赚取租金.英文原名monopoly意为“垄断”,因为最后只得一个胜利者,其余均破产收场. <大富翁>游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想. 主要需求 可多人参与的大富翁游戏,玩家有初始资金,通过掷骰子,玩家移动指定骰子点数步骤,根据对应格子上的交易策略,来决定是赚钱还是亏钱,其他玩家破

  • Java设计模式之组合模式的示例详解

    目录 定义 原理类图 案例 需求 方案 分析 总结 定义 组合模式,又叫部分整体模式,它创建了对象组的数据结构(将对象组合成树状结构,用来表示部分整体的层级关系)组合模式使得用户对单个对象和组合对象的访问具有一致性 原理类图 Component :这是组合模式中的抽象构件,他里面定义了所有类共有的默认行为,用来访问和管理Component的子部件,Component可以是抽象类,也可以是接口 leaf :在组合模式中表示叶子节点,叶子节点没有子节点了,他是最末端存放数据的结构 Composite

  • Java设计模式之享元模式示例详解

    目录 定义 原理类图 案例 需求 方案:享元模式 分析 总结 定义 享元模式(FlyWeight Pattern),也叫蝇量模式,运用共享技术,有效的支持大量细粒度的对象,享元模式就是池技术的重要实现方式. 原理类图 Flyweight :抽象的享元角色,他是抽象的产品类,同时他会定义出对象的内部状态和外部状态 ConcreteFlyweight :是具体的享元角色,具体的产品类,实现抽象角色,实现具体的业务逻辑 UnsharedConcreteFlyweight :不可共享的角色,这个角色也可

  • Java实现HashMap排序方法的示例详解

    目录 简介 排序已有数据 按key排序 按value排序 按插入顺序存放 HashMap不按插入顺序存放 LinkedHashMap会按照插入顺序存放 简介 本文用示例介绍HashMap排序的方法. 排序已有数据 按key排序 使用stream进行排序(按key升序/降序) package org.example.a; import java.util.*; public class Demo { public static void main(String[] args) { Map<Stri

  • Kotlin协程Dispatchers原理示例详解

    目录 前置知识 demo startCoroutineCancellable intercepted()函数 DefaultScheduler中找dispatch函数 Runnable传入 Worker线程执行逻辑 小结 前置知识 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从本质上讲,Kotlin和Java是类似的,都是可以编译产生class的语言,但最终还是会受到虚拟机的限制,它们的代码最终会在虚拟机上的某个线程上被执行. 之

随机推荐