`
suichangkele
  • 浏览: 200382 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

juc - Condition源码解读

    博客分类:
  • java
阅读更多

之前写过ReentrantLock的源码的博客,但是还有个重要的东西没有介绍——Condition,他的用法我就不介绍了(这种介绍性的博客我不喜欢写),我今天就写一下Condition的实现原理,还是从Conditon的常用方法入手。

1、await():这个方法由当前获得锁的线程调用,意思是释放锁,挂起自己并且唤醒等待当前线程持有的锁的其他线程(在aqs的等待队列中的其他节点),类似于synchronized同步代码块的wait方法的意思。

 public final void await() throws InterruptedException {
           if (Thread.interrupted())
               throw new InterruptedException();
           Node node = addConditionWaiter();//这个方法是将当前线程放到当前condition的队列中
           int savedState = fullyRelease(node);//释放当前线程站有的锁
           int interruptMode = 0;
           while (!isOnSyncQueue(node)) {//isOnSyncQueue的意思是判断当前当前的线程是否在sync的队列(sync的队列就是aqs的队列),while循环的意思是只要封装当前线程的node没有在aqs的队列中,就一直循环。
               LockSupport.park(this);//将当前的线程挂起,也就是说等当前的线程获得锁之后,还是从这里开始运行
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//这里是判断在线程挂起的时间内,有没有别的线程调用这个线程的iterrupt方法,如果没有的话返回0
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//挂起结束,也就是重新运行之后,调用acquireQueued方法,即进入aqs的队列中,acquireQueued方法在 http://suichangkele.iteye.com/blog/2368173  博客中已经讲了,就是获取锁的代码,如果获取失败则被挂起。
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

 下面把上面设计的方法一个一个的拆开看:

/**
* Adds a new waiter to wait queue. 添加一个新的等待者到wait queue中,注意这里是叫做wait queue,并不是上面说的sync,wait queue是condition自己维护的一个队列。
* @return its new wait node
*/
private Node addConditionWaiter() {
            Node t = lastWaiter;//这里的lastWaiter是wait queue的尾
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();//将取消的节点从队列中删除
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);//将当前的线程放入到wait queue中,也就是condition自己的队列中,节点的状态是condition.
            if (t == null)//
                firstWaiter = node;//如果当前的队列没有头,则置为头
            else
                t.nextWaiter = node;//有头的话,则一定有尾,将当前的节点放到尾的后面,
            lastWaiter = node;//更新队列的尾。
            return node;
        }
很简单就 看完了上面的代码,他就是将当前的线程放入到condition自己维护的队列中。全部操作都不是cas的,因为await方法只有当前获得锁的线程才可以调用,所以不会发生并发操作。

 

  下一个方法fullRelease方法,顾名思义,全部释放,他的真实意思是:将标记(标记的概念在 http://suichangkele.iteye.com/blog/2368173 这篇博客中)置为0,释放锁,

 

 final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {//将标记变为0,释放锁,并唤醒在aqs的等待队列中的线程。
                failed = false;
                return savedState;
            } 。。。。//下面还有很多,没贴
    }
 经过上面的两步,已经释放了锁,并且当前的线程已经加入了condition的wait queue中。

 

 再看是不是在aqs的队列中的代码:

final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)//如果node的状态是condition,则为false,因为condition的话说明是刚刚加入到condition的wait queue中,也就是刚才持有锁的线程,即使他在aqs的队列中的话他也一定是head,所以要重新放入到aqs的队列中;同理第二个判断node.prev==null,说明如果刚才持有
锁的线程的状态不是condition的话,如果是aqs的队列中的head,也不可以,因为head是持有锁的,就要将其挂起,也就是返回false。理解这一点很重要,也有点困难,他的最根本的逻辑是:如果当前的线程是持有锁的线程,则要将其挂起,否则不挂起。
            return false;
     。。。。。。//还有很多,删了
    }

上面的代码说明了一件事,如果当前线程持有锁,在调用了await之后就要放弃锁,被挂起,唤醒在aqs中等待的其他线程,然后加入到conditon的一个队列中。在唤醒正在aqs队列中的其他线程后,其他线程就会获得锁(这段代码在acquireQueued方法中)。但是需要注意的是,被加入到condition的wait queue的线程此时并不在aqs的等待队列中,也就是说他不会获得锁,如果要让他获得锁,就要看下面的signal方法。

 

 

2、signal(),他就是用来实现上面的问题的,他将在condition的wait queue中等待的线程转移到aqs的队列中:

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;//firstwaiter就是condition的队列中的head
            if (first != null)
                doSignal(first);
        }
 private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&//transferForSignal的意思就是转移first节点到aqs的队列中。
                     (first = firstWaiter) != null);//直到转移成功first或者队列中没有节点
        }

 

 final boolean transferForSignal(Node node) {
        /* If cannot change waitStatus, the node has been cancelled. */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//将node的waitStatus设置为0,如果设置失败,返回false,继续进行操作,设置成功向下走。
            return false;

        Node p = enq(node);//enq之前说过了,是将其加入到aqs的队列中
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//将node的waitStatus设置为signal,这样在aqs的队列中就和普通的等待着一样了。
            LockSupport.unpark(node.thread);
        return true;
    }

 通过signla方法,之前在condition的等待队列中的线程的head已经转移到aqs的队列中去等待获得锁了,这样就回到了之前ReentrantLock的博客中。

 

看完了源码,再举个例子,就能更好的理解了,我们举多个生产者和多个消费者的例子,生产者生产商品,消费者消费商品,生产出的商品放到容器中,容器中只能放一个商品。刚上来容器中为空,假设消费者获得锁,则所有的消费者在叫做empty的condition上等待,同时释放锁,这时,可能有一个或者多个消费者在empty上等待。此时生产者获得锁(因为只要是消费者获得锁就会进入empty的condition的队列中),将生产的商品放入到容器中,此时容器中已经有商品,其他的生产者发现不能放了,则在full这个condition上等待,也就是进入full这个condition的队列中,生产者放入后,会调用empty.signal,意思是将在empty中等待的线程转移到aqs的队列中,准备获得锁,然后生产者释放所,   消费者获得锁(因为现在容器中有商品了,虽然生产者有机会获得锁,但是只要是生产者持有锁,就会调用full.await,也就是现在条件不满足,虽然我获得了锁,但是必须要等待消费者消费,于是进入full的队列中,所以最终是消费者获得锁)。消费者获得锁后将商品消费,然后调用full.signal,也就是通知生产者生产,这样就回到了最开始的时候。。。。。

 

3、awaitNanos(long nanosTimeout) 带有时间的等待(这个是我后来补充的)

public final long awaitNanos(long nanosTimeout) throws InterruptedException {
       if (Thread.interrupted())//如果当前线程被其他的线程调用interrupt方法,则抛出异常
          throw new InterruptedException();
      Node node = addConditionWaiter();//将当前线程加入到condtion的等待队列中
      int savedState = fullyRelease(node);//释放当前占有的锁
      long lastTime = System.nanoTime();
      int interruptMode = 0;
      while (!isOnSyncQueue(node)) {//循环,直到添加到aqs的队列中
          if (nanosTimeout <= 0L) {//刚上来nanosTimeout不是<=0的,不过后面的parkNanos之后就会符合这个条件。
              transferAfterCancelledWait(node);//这个方法的意思是如果等待的时间足够长了,则转移到aqs的队列中,他是有时间的condition的关键的方法
                  break;
          }
          LockSupport.parkNanos(this, nanosTimeout);//将当前的线程挂起指定的时间,
          if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//在挂起期间内如果调用iterrupt方法则跳出循环。
              break;
          long now = System.nanoTime();
          nanosTimeout -= now - lastTime;//挂起指定时间后继续执行,减小nanosTimeout的值
          lastTime = now;
      }
      if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//在
          interruptMode = REINTERRUPT;
      if (node.nextWaiter != null)
          unlinkCancelledWaiters();
      if (interruptMode != 0)
          reportInterruptAfterWait(interruptMode);
      return nanosTimeout - (System.nanoTime() - lastTime);
 } 

 

transferAfterCancelledWait方法很重要,看看他的实现:

final boolean transferAfterCancelledWait(Node node) {//参数是等待了足够长时间的节点(也就是线程)
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {//如果成功的将节点的属性设置为condition,则将其添加到aqs的等待的队列中
            enq(node);//进入到aqs的队列中
            return true;
        }
        //上面的代码唯一失败的可能是在此期间,condition被调用了signal方法,导致上面的compareAndSetWaitStatus返回false,这时更要将当前线程放入到aqs的队列了,所以调用了当前线程的yield方法,
        while (!isOnSyncQueue(node))
            Thread.yield();//当前线程让步,让另一个线程执行。等待另一个线程(也就是调用condition的signal的方法的线程)执行enq完成。
        return false;
    }

从这个方法可以看出,如果 在有时间的condition中,在时间消耗完成之后并不是会退出,而是直接进入到aqs的队列中等待获得锁。

 

condition就这么简单,就是内部维持了一个队列,在调用await的时候将线程阻塞,释放锁,将其加入到队列中等待,在调用signal的时候将自己队列中的head转移到aqs的队列中,等待获得锁。

 

分享到:
评论

相关推荐

    tuling-juc-final.zip

    本项目"tuling-juc-final.zip"显然聚焦于Java并发编程的实践,通过一系列代码示例来演示和解释Java内存模型(JMM)、`synchronized`关键字以及`volatile`关键字的使用。下面我们将深入探讨这些核心概念。 Java内存...

    Java 多线程与并发(7-26)-JUC - 类汇总和学习指南.pdf

    "Java 多线程与并发(7-26)-JUC - 类汇总和学习指南" Java 多线程与并发是 Java 编程语言中的一部分,用于处理多线程和并发编程。Java 提供了一个名为 JUC(Java Utilities for Concurrency)的框架,用于帮助开发者...

    Java-JUC-多线程 进阶

    Java-JUC-多线程进阶 Java-JUC-多线程进阶resources是 Java 并发编程的高级课程,涵盖了 Java 中的并发编程概念、线程安全、锁机制、集合类、线程池、函数式接口、Stream流式计算等多个方面。 什么是JUC JUC...

    个人学习-JUC-笔记

    - **Condition**:Lock接口的扩展,提供更灵活的唤醒等待机制。 - **CountDownLatch/CyclicBarrier/Semaphore**:信号量类,用于控制并发线程数量或同步点。 8. **FutureTask** - **FutureTask**:表示一个异步...

    heima-JUC-资料

    Java并发编程是Java Util Concurrency(JUC)库的核心内容,它为多线程环境提供了高效、安全且灵活的工具和API。JUC是Java 5及后续版本引入的一个重要特性,极大地提升了Java在多处理器和高并发环境下的性能表现。 ...

    juc-study编程笔记.md

    学习狂神说的juc编程的笔记

    A-JUC-JVM-Java并发知识..pdf

    ### JUC并发编程 #### JUC多线程及高并发 Java并发编程包(java.util.concurrent,简称JUC)封装了大量用于高并发编程的工具类和接口,其中涉及了线程池、阻塞队列、同步器、原子操作类等。在并发环境下,可以有效...

    juc-1(2).docx

    【JUC 概念】 Java Util Concurrency (JUC) 是 Java SDK 中的一个核心包,位于 `java.util.concurrent` 下,它提供了丰富的线程同步和并发工具类,旨在简化多线程编程,提高程序的并发性能。JUC 包含了线程池、并发...

    juc-jenkins-2018:JUC Jenkins 2018演示源代码

    juc-jenkins-2018 JUC Jenkins 2018演示源代码 先决条件 为了运行此演示,必须有一个有效的JDK,git命令以及curl。 克隆存储库 将此存储库克隆到您家中的某个位置: git clone ...

    juc_nio_linux.rar

    Java并发编程库(Java Util Concurrency,简称JUC)是Java平台中用于高效并发处理的重要工具,它提供了线程池、锁、原子变量等高级并发工具。在Java中,`java.util.concurrent`包包含了大量并发控制和并行计算的类与...

    JUC-3 微程序控制计算机系列实验资源

    微程序控制器实验1. 连接好实验线路,检查无误后接通电源。2. 将编程开关(MJ20)置为PROM(编程)状态。3. 将STATE UNIT中的STEP置为“STEP”状态,STOP置为“RUN”状态。4. 在UA5-UA0开关上置要写的某个微地址(八进制)...

    juc-learn:juc相关源码的分析以及使用介绍

    本项目"juc-learn"专注于JUC相关源码的分析和使用介绍,旨在帮助开发者深入理解并熟练运用这些并发工具。 1. **并发基础** 在Java中,多线程是并发编程的基础。通过创建Thread对象或实现Runnable接口,我们可以...

    JUC-master

    《JUC:Java并发编程的艺术》 在Java世界中,JUC(Java Util Concurrency)是并发编程的核心库,它提供了丰富的...通过深入学习JUC-master项目,我们可以更深入地理解Java并发编程的原理和实践,提升我们的编程技能。

    juc-demo:JUC包下常用工具练习Demo

    juc-demo JUC包下常用工具练习Demo 内容: 1、Semaphore 2、CountDownLatch 3、CyclicBarrier 4、ReentrantLock + Condition实现阻塞队列 Created by @minghui.y.

    免费开源!!主要是Java技术栈的文章

    1、Java并发体系-第一阶段-多线程基础知识 2、Java并发体系-第二阶段-锁与同步-[1] 3、Java并发体系-第二阶段-锁与同步-[2] 4、Java并发体系-第二阶段-锁与同步-[3] ...7、Java并发体系-第四阶段-AQS源码解读-[1]

    JUC线程高级

    JUC线程高级,

    JUC并发编程与源码分析视频课.zip

    《JUC并发编程与源码分析视频课》是一门深入探讨Java并发编程的课程,主要聚焦于Java Util Concurrency(JUC)库的使用和源码解析。JUC是Java平台提供的一组高级并发工具包,它极大地简化了多线程编程,并提供了更...

    22 尚硅谷Java JUC线程高级视频

    教程视频:在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类, 用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文...

    江苏大学计组课设指令编写

    江苏大学计组课设指令编写是计算机组成原理课程设计报告的一部分,该报告的主要内容是设计和实现 JUC2 模型机的微程序。下面是该报告的详细知识点解释: 1. 目标要求 计算机组成原理课程设计的目标是设计和实现一...

    JUC+课程源码+线程操作

    本课程资源主要围绕JUC进行展开,通过样例源码帮助学习者深入理解和掌握线程操作的相关知识。 在JUC中,核心组件包括`ExecutorService`、`Semaphore`、`CountDownLatch`、`CyclicBarrier`、`Future`、`...

Global site tag (gtag.js) - Google Analytics