`

java条件队列—Condition

阅读更多

前言

 

Conditionjava1.5在引入显示锁Lock的同时一起引入的,它是一个接口,定义了一些列的await方法和signalsignalAll方法。其作用与Object内置锁对象的waitnotifynotifyAll方法类似,用于在某种条件下对线程进行阻塞和唤醒,这些条件就构成了条件队列。只是Condition方法在java API中进行了显式的实现;而内置锁使用的Object类中的相关方法都是native方法,是由jvm实现的。

 

Condition一般都是配合一个显式锁Lock一起使用,Lock接口的方法中有一个newCondition()方法用于生成Condition对象。在java1.5以后常用的显式锁有ReentrantLockReentrantReadWriteLock,他们都是基于AQS实现的,而在AQS中有一个内部类ConditionObject实现了Condition接口。所谓条件队列,其实是一个单向链表;在讲解AQS的实现原理时只讲解了AQS队列,AQS队列前面讲过(点这里)是双向链表结构。也就是说在AQS整体实现中维护了两个链表:一个是同步队列双向链表(这里简称AQS队列),另一个是条件队列单向链表。

 

这里以ReentrantLock为例先简单讲解下这两个队列的关系:通过ReentrantLocklock方法,如果获取不到锁当前线程会进入AQS队列阻塞;被唤醒后继续获取锁,如果获取到锁,移出AQS队列,继续执行;遇到Conditionawait方法,加入条件队列,阻塞线程;被其他现象的signal方法唤醒,从条件队列中删除,并加入到AQS队列,如果获取到锁就继续执行。可以看到上述操作,线程节点(Node)其实在两个队列之间切换,由于条件队列在被唤醒时 都是从头开始遍历,所以只需要使用单向链表实现即可。在探究原理之前,先来看看Condition的相关方法、以及基本使用方式。

 

Condition的主要方法

 

Condition接口中一共定义了5await方法,一个signal方法,一个signalAll方法。

 

5await方法:

1、最基本的await方法: void await() throws InterruptedException; 使用这个方法必须在一个显式锁的lockunlock包围的代码块之间;调用该方法后,当前线程会释放锁并被阻塞,直到其他线程通过调用同一个Condition对象的signal或者signalAll方法,再次被唤醒(唤醒后继续抢锁)。该方法会抛出InterruptedException异常,也就是说是可中断的(内置锁使用Object对象的三个wait方法也是可中断的)。

2、不可中断的await方法:void awaitUninterruptibly();该方法与await方法的作用相同,区别就是awaitUninterruptibly是不可中断的。也就是说,只能通过其他线程调用同一个Condition对象的signal或者signalAll方法,才能被唤醒。

3、延时wait方法(返回long):long awaitNanos(long nanosTimeout) throws InterruptedException;这个方法基本作用与await方法相同,区别就是通过awaitNanos方法阻塞的线程,如果在指定的时间内还没有被signal或者signalAll方法唤醒,则会阻塞指定时间后自动取消阻塞,并返回;返回值 nanostimeout 值减去花费在等待此方法的返回结果的时间的估算值,如果返回值如果小于等于0说明超时(在指定时间内没有被signal或者signalAll方法唤醒)。

4、延时wait方法(返回boolean):boolean await(long time, TimeUnit unit) throws InterruptedException;这个方法与awaitNanos作用完全一样,区别只在返回值。如果返回true,相当于awaitNanos返回大于0,如果返回false相当于awaitNanos返回小于等于0

5、延时到指定时间wait方法: oolean awaitUntil(Date deadline) throws InterruptedException;这个方法与第4个方法作用相同,只是参数有区别,这个方法依赖服务器的时钟。

5个方法除了第二以外,其他的都是支持中断的。

 

signal方法:唤醒条件队列中的1个线程(await时间最长的线程)。

signalAll方法:唤醒条件队列中所有的线程,去竞争。

 

可见使用signal方法的性能肯定会好些,但有可能有些线程会被忘了唤醒(延时await可以解决这个问题)。signalAll方法性能差些,但能保证所有的线程最终都会被唤醒,使用方便。这两个方法可以根据具体业务情况使用。

 

ConditionObject的实现原理

 

AQS的内部类ConditionObjectCondition的所有接口方法进行了实现,5await方法的核心实现基本相同,这里只对第一个await方法进行分析。另外在对signalsignalAll方法的实现原理进行分析。在对这三个方法进行分析之前,首先来看下条件队列,在ConditionObject中定了条件队列的第一个节点和最后一个节点:

/** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

Node中还有一个指向下一个节点的指针:nextWaiter,这就构成了一个单向链表的条件队列

 

await方法

public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        Node node = addConditionWaiter();//加入条件队列,注意不是AQS队列
        long savedState = fullyRelease(node);//释放当前线程占用的排它锁
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {//判断当前节点是否在AQS队列中,如果不在就进行阻塞
            LockSupport.park(this);//阻塞等待signal
            //判断中断标记在阻塞等待期间 是否改变
            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);
    }
 

 

这里的重点就是如何跳出while循环,一般有两种情况:被外部调用interrupt方法中断,这时会在break处跳出;在Condition上调用signal,这时会把该节点从条件队列移到AQS队列,whlie循环继续调用isOnSyncQueue方法检查,这时当前线程已经在AQS队列中存在,跳出while循环。另外unlinkCancelledWaiters方法会清除已经清理已经处理过的节点,即从条件队列中移除。

 

signal方法

signal本质上就是把条件队列的第一个节点移除,并加入到“AQS队列”:

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;//取出第一个节点
            if (first != null)
                doSignal(first);//见下方
        }
 
private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) && //转移方法,见下方
                     (first = firstWaiter) != null);
        }
final boolean transferForSignal(Node node) {
        //更改变线程状态
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //添加到AQS队列
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);//唤醒线程,在await内部park阻塞的
        return true;
}

transferForSignal方法中可以看到节点会被添加到“AQS队列中,排队获取锁。如果取得锁,就可以执行执行,否则还是会在AQS队列中继续被阻塞。

 

signalAll方法

signalAll方法与signal的区别是,signalAll会遍历整个条件队列,唤醒所有线程加入到“AQS队列中:

public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);//区别
        }
private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);//遍历到队列末尾
        }
 

 

至此三个核心方法分析完毕,其他await方法实现大同小异。

 

总结

 

AQS中本质上有两个队列,一个是AQS队列,一个是条件队列。AQS队列一般用于各种锁的实现,条件队列必须结合锁一起使用,通过await方法加入条件队列,通过signal方法可以把节点移动到“AQS队列,并触发从条件队列中移除(在await方法返回前)。

 

 

最后简单提下,Thread.sleepawait(或Objectwait方法)的区别, sleep方法本质上不会放弃锁;而await会放弃锁,并在signal后,还需重新获得锁 才能继续执行。

 

 

 

0
0
分享到:
评论

相关推荐

    带你看看Java-AQS同步器 源码解读四 条件队列Condition上

    Java-AQS同步器 源码解读-条件队列Condition前文为什么需要条件队列Conditon Queue举个小例子分析怎么使用条件队列写个小DemoJDK中是怎么使用的Lock和ConditionLockConditionSync-Queue和Conditian-QueueAQS ...

    Java并发之条件阻塞Condition的应用代码示例

    主要介绍了Java并发之条件阻塞Condition的应用代码示例,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

    Java可阻塞队列-ArrayBlockingQueue

    在前面的的文章,写了一个带有缓冲区的队列,是用JAVA的Lock下的Condition实现的,但是JAVA类中提供了这项功能,是ArrayBlockingQueue,  ArrayBlockingQueue是由数组支持的有界阻塞队列,次队列按照FIFO(先进先...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    11.2.1 lock、readwritelock与condition 349 11.2.2 使用executor 357 11.2.3 并行collection简介 370 11.3 重点复习 373 11.4 课后练习 375 chapter12 通用api 377 12.1 日志 378 12.1.1 日志api简介...

    Java并发编程原理与实战

    使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与实现原理剖析.mp4 ThreadLocal 使用及实现原理.mp4 并发工具类...

    Java 7并发编程实战手册

    2.8 在锁中使用多条件(Multiple Condition) 69 第3章 线程同步辅助类 77 3.1 简介 77 3.2 资源的并发访问控制 78 3.3 资源的多副本的并发访问控制 83 3.4 等待多个并发事件的完成 87 3.5 在集合...

    Java JDK实例宝典

    14 线程——条件Condition 16. 15 线程——Semaphore 16. 16 线程——CountDownLatch 16. 17 线程——Cycli Barrier 16. 18 线程——Exchanger 16. 19 线程——BlockingQueue 第17章 Java...

    java多线程实现生产者和消费者

    java多线程实现生产者和消费者 ,4种实现方式,分别为synchronizated,condition和lock,信号量,阻塞队列

    龙果java并发编程完整视频

    第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析...

    Java SE实践教程 pdf格式电子书 下载(四) 更新

    6.2.3 Condition对象 121 6.2.4 再谈synchronized 122 6.3 协调任务 124 6.3.1 线程池和Executor 124 6.3.2 Callable和Future 126 6.3.3 ScheduledExecutorService 127 6.4 线程安全的集合和同步器 128 6.4.1...

    Java并发编程实战

    14.2.7 封装条件队列250 14.2.8 入口协议与出口协议250 14.3 显式的Condition对象251 14.4 Synchronizer剖析253 14.5 AbstractQueuedSynchronizer254 14.6 java.util.concurrent同步器类中的 AQS257 14.6.1 ...

    Java并发编程相关技术使用案例

    线程创建、Synchronized和Reentrantlock锁的使用、线程安全问题演示、Condition的应用、CountDownLatch的应用、Cyclicbarrier的应用、Semaphore的应用、线程池的应用、Completablefuture的应用、手写阻塞队列、fork...

    龙果 java并发编程原理实战

    第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析...

    基础技术部牛路《Java多线程入阶分享》纯干货

    Java多线程入阶干货分享 1.使用线程的经验:设置名称、响应中断、使用ThreadLocal 2.Executor:ExecutorService和Future 3.阻塞队列:put和take、offer和poll、drainTo 4.线程间通信:lock、condition、wait、notify...

    Java SE实践教程 源代码 下载

    6.2.3 Condition对象 121 6.2.4 再谈synchronized 122 6.3 协调任务 124 6.3.1 线程池和Executor 124 6.3.2 Callable和Future 126 6.3.3 ScheduledExecutorService 127 6.4 线程安全的集合和同步器 128 6.4.1...

    Java SE实践教程 pdf格式电子书 下载(一) 更新

    6.2.3 Condition对象 121 6.2.4 再谈synchronized 122 6.3 协调任务 124 6.3.1 线程池和Executor 124 6.3.2 Callable和Future 126 6.3.3 ScheduledExecutorService 127 6.4 线程安全的集合和同步器 128 6.4.1...

    Java并发程序设计教程

    3、阻塞队列: put和take、offer和poll、drainTo 4、线程间的协调手段:lock、condition、wait、notify、notifyAll☆☆☆ 5、Lock-free: atomic、concurrentMap.putIfAbsent、CopyOnWriteArrayList☆☆☆ 6、关于锁...

    阿里Java并发程序设计教程

    3、阻塞队列 : put和take、offer和poll、drainTo 4、线程间的协调手段:lock、condition、wait、notify、notifyAll ☆ ☆ ☆ 5、Lock-free: atomic、concurrentMap.putIfAbsent、CopyOnWriteArrayList ☆ ☆ ☆ 6、...

    Java 并发编程原理与实战视频

    第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析...

Global site tag (gtag.js) - Google Analytics