`

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并发系列之AbstractQueuedSynchronizer源码分析(条件队列)

    条件队列是AQS中与`Condition`接口相关的部分,它允许线程在满足特定条件时等待,而不是简单地阻塞在同步状态上。条件队列是由`ConditionObject`类实现的,该类实现了`Condition`接口。当线程需要等待某个条件时,它...

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

    在Java中,`java.util.concurrent.locks.Condition`接口是条件队列的抽象,它允许线程在等待特定条件时挂起,并在条件满足时被唤醒。与传统的`wait()`和`notify()`方法相比,`Condition`提供了更细粒度的控制,更...

    Java concurrency之Condition条件_动力节点Java学院整理

    `Condition`允许我们创建多个独立的等待队列,每个队列对应一个特定的条件,从而实现对锁的更加灵活的控制。下面我们将深入探讨`Condition`的相关知识点。 ### Condition的基本概念 1. **同步锁**:`Condition`...

    java队列源码

    在这个"java队列源码"中,我们可以看到如何利用Java来实现多线程环境下的安全队列,特别适用于抢购等高并发场景。以下将详细讨论队列、多线程以及源码实现的关键知识点。 1. **Java 队列接口与实现** - Java 提供...

    java多线程模拟队列实现排队叫号

    在Java中,我们可以使用`java.util.Queue`接口及其实现类,如`LinkedList`或`ArrayDeque`来创建队列。 接下来,我们需要创建两个线程类:一个是`CustomerThread`,代表等待叫号的客户,另一个是`ServiceThread`,...

    JavaLock与Condition的理解Reentran

    Java Lock与Condition是Java并发编程中的重要概念,它们提供了比synchronized关键字更细粒度的控制,从而使得多线程程序的设计和管理更加灵活高效。本文将深入探讨ReentrantLock(可重入锁)和Condition的基本原理、...

    java阻塞队列实现原理及实例解析.docx

    Java 阻塞队列(Blocking Queue)是一种特殊类型的并发数据结构,它在多线程编程中扮演着重要的角色。阻塞队列的核心特性在于其在队列为空或满时能够自动阻塞线程,从而实现线程间的同步和通信。这种机制使得生产者...

    java阻塞队列实现原理及实例解析

    Java阻塞队列实现原理及实例解析 Java阻塞队列是一种特殊的队列,它能够在队列为空或满时阻塞线程,使得线程之间能够更好地协作和通信。阻塞队列的实现原理是基于锁机制和条件变量机制的,通过wait和notify方法来...

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

    Java并发之条件阻塞Condition的应用代码示例 Java并发之条件阻塞Condition是Java并发编程中的一种高级同步机制,它允许线程在某个条件下等待或唤醒其他线程。Condition将Object监视器方法(wait、notify和notifyAll...

    Java并发显式锁和显式条件队列

    **显式条件队列**,通常与显式锁配合使用,如`java.util.concurrent.locks.Condition`接口。条件队列允许线程等待特定条件满足后再继续执行,而不是简单地等待锁的释放。条件队列的线程等待和唤醒是通过`Condition`...

    Java多线程之ReentrantLock与Condition - 平凡希 - 博客园1

    - **Condition**:`ReentrantLock`支持多个条件变量,每个`Condition`对象都有自己的等待队列,可以实现更细粒度的同步控制。与`synchronized`的`wait()`和`notifyAll()`不同,`Condition`提供了`await()`和`signal...

    剖析Java中阻塞队列的实现原理及应用场景

    阻塞队列的实现依赖于Java的并发原语,如`Lock`和`Condition`。在内部,它们使用了等待/通知机制,当队列状态发生变化时(如添加或移除元素),会通过`Condition`对象通知等待的线程,使得线程可以被唤醒继续执行。...

    java中的Lock类和Condition类.docx

    Java中的Lock类与Condition类是Java并发编程的重要组成部分,它们为多线程环境下的同步提供了更为灵活和强大的控制。在JDK 1.5及之后的版本中,Lock类作为替代synchronized关键字的一种方式出现,提供了更精细的锁...

    Java学习资料-详解Condition的await和signal等待/通知机制

    - `Condition`是`Lock`接口的一个方法,用于创建特定条件的等待/通知机制。与`Object`类的`wait()`、`notify()`和`notifyAll()`相比,`Condition`提供了更细粒度的控制。 - `Condition`支持多个等待队列,每个`...

    基于JAVA的分布式聊天系统.zip_8SMV_JAVA分布式_condition6md_分布式_基于JAVA的分布式聊天系统

    《基于JAVA的分布式聊天系统详解》 在信息技术日益发展的今天,分布式系统已经成为构建大型、高可用、可扩展网络应用的主流方案。Java以其强大的跨平台能力和丰富的库支持,成为实现分布式系统的理想选择。本篇文章...

    支持多线程和泛型的阻塞队列

    C++中,虽然标准库没有内置阻塞队列,但可以利用互斥锁(mutex)、条件变量(condition variable)等同步原语自定义实现。 ### 泛型 泛型是现代编程语言中的一个重要特性,允许在不指定具体类型的情况下编写代码,...

    Java编程中实现Condition控制线程通信

    Condition接口是Java并发包java.util.concurrent.locks的一部分,它允许我们创建特定于锁的等待集合。相比于Object的监视器方法,Condition提供了更大的灵活性,可以为每个Lock创建多个Condition实例,从而更好地...

    Java可阻塞队列-ArrayBlockingQueue

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

Global site tag (gtag.js) - Google Analytics