文档章节

java并发系列-lock-AQS-condition

南寒之星
 南寒之星
发布于 2016/11/15 11:51
字数 2187
阅读 132
收藏 1

一、背景

上一篇已经解释了ReentrantLock,AQS基本框架

AQS是JUC重要的同步器,所有的锁基于这个同步器实现,先温习下 主要由以下几个重要部分组成

  • Node 节点
  • head 头节点
  • tail 尾节点
  • state 当前锁的状态
  • acquire(int arg) 获取锁
  • acquireQueued(final Node node, int arg) 获取锁队列
  • addWaiter(Node mode) 加入等待队列
  • release(int) 释放锁
  • unparkSuccessor(Node) 唤醒继任节点
  • ConditionObject 条件对象,功能类似wait/notify

那么今天就是重点介绍AQS中的ConditionObject 功能和实现。

二、什么是condition

在AQS中,Node节点通过nextWaiter指针串起来的就是条件队列,中有ConditionObject对象,实现接口Condition,由具体的lock,如ReentrantLock.newCondition创建和调用。

Condition主要接口有:

public interface Condition {  
    void await() throws InterruptedException;  
    void awaitUninterruptibly();  
    long awaitNanos(long nanosTimeout) throws InterruptedException;  
    boolean await(long time, TimeUnit unit) throws InterruptedException;  
    boolean awaitUntil(Date deadline) throws InterruptedException;  
    void signal();  
    void signalAll();  
}  
  • 相同点:

在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现。

  • 不同点:

**比如一个对象里面可以有多个Condition,可以注册在不同的condition,可以有选择性的调度线程,很灵活。而Synchronized只有一个condition(就是对象本身),所有的线程都注册在这个conditon身上,线程调度不灵活。 ** Condition和传统的线程通信没什么区别,Condition的强大之处在于它可以为多个线程间建立不同的Condition,下面引入API中的一段代码(原文地址,http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html),ArrayBlockingQueue 提供了类似的功能,实习原理类似。

class BoundedBuffer {  
   final Lock lock = new ReentrantLock();//锁对象  
   final Condition notFull  = lock.newCondition();//写线程条件   
   final Condition notEmpty = lock.newCondition();//读线程条件   
  
   final Object[] items = new Object[100];//缓存队列  
   int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;  
  
   public void put(Object x) throws InterruptedException {  
     lock.lock();  
     try {  
       while (count == items.length)//如果队列满了   
         notFull.await();//阻塞写线程  
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0  
       ++count;//个数++  
       notEmpty.signal();//唤醒读线程  
     } finally {  
       lock.unlock();  
     }  
   }  
  
   public Object take() throws InterruptedException {  
     lock.lock();  
     try {  
       while (count == 0)//如果队列为空  
         notEmpty.await();//阻塞读线程  
       Object x = items[takeptr];//取值   
       if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0  
       --count;//个数--  
       notFull.signal();//唤醒写线程  
       return x;  
     } finally {  
       lock.unlock();  
     }  
   }   
 }  

在多线程环境下,缓存区提供了两个方法,put and take,put是存数据的,take是取数据的,items充当缓存队列,运行场景是有多个线程同时往调用put和take;这里的读和写共用的其实是一把锁lock,实际读写不能同时并行;当写入一个数据后,便通知读线程,写满后调用notFull.await()阻塞写;当读入一个数据后,便通知写线程,读空后调用notEmpty.await()阻塞读,等待写条件通知信号;

三、condition源码实现

接下来我们讲解AQS.ConditionObject 内部源码。 主要分析以下几个方法:

  • await 阻塞线程,加入条件队列,等待条件信号释放
  1. addConditionWaiter
  2. fullyRelease
  3. isOnSyncQueue
  • signal 释放条件信号,加入锁等待队列syc,等待获取锁
  1. doSignal
  2. transferForSignal

这里我先定义下两个概念:

  1. 锁等待队列(同步队列) 即获取锁后等待解锁的队列,AQS中的syc (FIFO)
  2. 条件等待队列 即调用await后进入条件队列,等待signal释放信号,进入锁等待队列(FIFO),ConditionObject 中

await

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();  //添加到Condition自己维护的一个链表中,通过nextWaiter连接起来,即条件等待队列  
            int savedState = fullyRelease(node); //释当前节点的锁,并返回释放之前的锁状态,执行await一定是先拿到了当前锁的
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {  //判断该节点是否在锁等待队列中,当释放锁后就不在锁队列中了,等待条件队列的线程应当被继续阻塞,如果在锁等待队列中,则说明有资格获取锁,执行下一步,聪明的你发现是在signal时被再次加入锁等待队列
                LockSupport.park(this); //在条件队列中阻塞该线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
        // 被唤醒后,重新开始正式竞争锁,同样,如果竞争不到还是会将自己沉睡,等待唤醒重新开始竞争。
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // 存在下一个节点,从条件队列中取消不是在CONDITION的线程节点
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter

当前waiter线程加入条件等待队列里,从队尾入队,并将节点线程状态设置为CONDITION

 /**
         *当前waiter线程加入条件等待队列里,从队尾入队,并将节点线程状态设置为CONDITION
         * [@return](https://my.oschina.net/u/556800) 返回新的节点
         */
        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;
        }

fullyRelease:

释当前节点的锁,并返回释放之前的锁状态,执行await一定是先拿到了当前锁的

 /**
     * Invokes release with current state value; returns saved state.
     * Cancels node and throws exception on failure.
     * @param node the condition node for this wait
     * @return previous sync state
     */

//释当前节点的锁,并返回释放之前的锁状态,执行await一定是先拿到了当前锁的

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

isOnSyncQueue

判断这个节点是否在锁等待队列中,显然waitStatus为CONDITION这种结点,只属于条件队列,不在锁等待队列中。


判断这个节点是否在锁等待队列中,显然waitStatus为CONDITION这种结点,只属于条件队列,不在锁等待队列中。
   
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        return findNodeFromTail(node);
    }

signal

从条件队列中的头节点开始,释放第一个节点线程,加入到锁等待队列


//从条件队列中的头节点开始,释放第一个节点线程,加入到锁等待队列
        public final void signal() {
            //当前线程不是拥有锁线程这抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;//条件队列中第一个节点
         
            if (first != null)
                doSignal(first);
        }

doSignal

doSignal()方法是一个while循环,直到transferForSignal成功为止, 不断地完成这样一个transfer(条件队列--->锁同步等待队列)操作,直到有一个成功为止。

 /**
         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */

        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                //将第一个节点的继任变量设置空,意义是first与next节点断掉,便于gc
                first.nextWaiter = null;
              //  将节点从条件队列转移到锁等待队列,如果成功则返回true,while循环终止,如果转移失败,则从first往后找,聪明的你肯定就知道,如果是doSignalAll,这从first一直往后执行transferForSignal
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

transferForSignal

将节点从条件队列转移到锁等待队列,如果成功则返回true transferForSignal做了两件事

  1. 将结点从条件队列移到锁同步等待队列中
  2. 设置同步中它的前趋结点的waitStatus为SIGNAL,并挂起当前线程。
 /**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal).
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
 //假设在条件队列中,将node状态设置0,如果waitStatus不能被更改,则该节点已经被取消
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
       //通过enq将节点加入到锁等待队列,从队尾插入,并返回前驱节点。
        Node p = enq(node);
        int ws = p.waitStatus;
      //如果该前驱节点被取消或者更改waitStatus状态为SIGNAL失败,说明前驱失效,则唤醒在锁同步等待队列当前节点
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

四、总结

。。。待续

© 著作权归作者所有

南寒之星
粉丝 11
博文 40
码字总数 39755
作品 0
杭州
高级程序员
私信 提问
加载中

评论(1)

蒙仔
蒙仔
有干货!支持
【Java并发专题】27篇文章详细总结Java并发基础知识

努力的意义,就是,在以后的日子里,放眼望去全是自己喜欢的人和事! github:https://github.com/CL0610/Java-concurrency,欢迎题issue和Pull request。所有的文档都是自己亲自码的,如果觉...

你听___
2018/05/06
0
0
【死磕Java并发】—– 死磕 Java 并发精品合集

【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览。 先来一个总览图: 【高清图,请关注“Java技术驿站”公众号,回复:脑...

chenssy
2018/07/22
0
0
Java并发同步器AQS(AbstractQueuedSynchronizer)学习笔记(1)

Java中的并发包,是在Java代码中并发程序的热门话题。如果我们去读concurrent包的源码时,会发现其真正的核心是 AbstractQueuedSynchronizer , 简称 AQS 框架 , 而 Doug Lea 大神正是此包的...

zavakid
2012/10/24
0
0
读书笔记之《Java并发编程的艺术》-java中的锁

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
0
Java 并发编程源码解析汇总篇

java并发编程,内存模型 java并发编程,volatile内存实现和原理 Java并发编程,并发基础 Java 并发编程,线程池(ThreadPoolExecutor)源码解析 Java并发编程,Executor 框架介绍 Java并发编...

郑加威
2018/12/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

数据库

数据库架构 数据库架构可以分为存储文件系统和程序实例两大块,而程序实例根据不同的功能又可以分为如下小模块。 1550644570798 索引模块 常见的问题有: 为什么要使用索引 什么样的信息能成...

一只小青蛙
今天
4
0
PHP常用经典算法实现

<? //-------------------- // 基本数据结构算法 //-------------------- //二分查找(数组里查找某个元素) function bin_sch($array, $low, $high, $k){ if ( $low <= $high){ $mid = int......

半缘修道半缘君丶
昨天
5
0
GIL 已经被杀死了么?

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。 原文地址:https://mp.weixin.qq.com/s/8KvQemz0SWq2hw-2aBPv2Q 花下猫语: Python 中最广为人诟病的一点,大概就是它的 GIL 了。...

豌豆花下猫
昨天
5
0
git commit message form

commit message一般包括3部分:Header、Body、Footer。 <type>(<scope>):<subject>blank line<body>blank line<footer> header是必需的,body、footer可以省略。 header中type、subject......

ninjaFrog
昨天
5
0
聊聊Elasticsearch的CircuitBreakerService

序 本文主要研究一下Elasticsearch的CircuitBreakerService CircuitBreakerService elasticsearch-7.0.1/server/src/main/java/org/elasticsearch/indices/breaker/CircuitBreakerService.ja......

go4it
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部