admin管理员组文章数量:1584824
Condition接口
Contition是一种广义上的条件队列,它利用await()和signal()为线程提供了一种更为灵活的等待/通知模式。
图源:《Java并发编程的艺术》
Condition必须要配合Lock一起使用,因为对共享状态变量的访问发生在多线程环境下。
一个Condition的实例必须与一个Lock绑定,因此await和signal的调用必须在lock和unlock之间,有锁之后,才能使用condition嘛。以ReentrantLock为例,简单使用如下:
public class ConditionTest {
public static void main(String[] args) {
final ReentrantLock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
Thread thread1 = new Thread(() -> {
String name = Thread.currentThread().getName();
lock.lock();
System.out.println(name + " <==成功获取到锁" + lock);
try {
System.out.println(name + " <==进入条件队列等待");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " <==醒了");
lock.unlock();
System.out.println(name + " <==释放锁");
}, "等待线程");
thread1.start();
Thread thread2 = new Thread(() -> {
String name = Thread.currentThread().getName();
lock.lock();
System.out.println(name + " ==>成功获取到锁" + lock);
try {
System.out.println("========== 这里演示await中的线程没有被signal的时候会一直等着 ===========");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " ==>通知等待队列的线程");
condition.signal();
lock.unlock();
System.out.println(name + " ==>释放锁");
}, "通知线程");
thread2.start();
}
}
等待线程 <==成功获取到锁java.util.concurrent.locks.ReentrantLock@3642cea8[Locked by thread 等待线程]
等待线程 <==进入条件队列等待
通知线程 ==>成功获取到锁java.util.concurrent.locks.ReentrantLock@3642cea8[Locked by thread 通知线程]
========== 这里演示await中的线程没有被signal的时候会一直等着 ===========
通知线程 ==>通知等待队列的线程
通知线程 ==>释放锁
等待线程 <==醒了
等待线程 <==释放锁
接下来我们将从源码的角度分析上面这个流程,理解所谓条件队列的内涵。
AQS条件变量的支持之ConditionObject内部类
AQS,Lock,Condition,ConditionObject
之间的关系:
ConditionObject是AQS的内部类,实现了Condition接口,Lock中提供newCondition()方法,委托给内部AQS的实现Sync来创建ConditionObject对象,享受AQS对Condition的支持。
// ReentrantLock#newCondition
public Condition newCondition() {
return sync.newCondition();
}
// Sync#newCondition
final ConditionObject newCondition() {
// 返回Contition的实现,定义在AQS中
return new ConditionObject();
}
ConditionObject用来结合锁实现线程同步,ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。
ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await方法后被阻塞的线程,ConditionObject维护了首尾节点,没错这里的Node就是我们之前在学习AQS的时候见到的那个Node,我们会在下面回顾:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** 条件队列的第一个节点. */
private transient Node firstWaiter;
/** 条件队列的最后一个节点. */
private transient Node lastWaiter;
}
看到这里我们需要明确这里的条件队列和我们之前说的AQS同步队列是不一样的:
-
AQS维护的是当前在等待资源的队列,Condition维护的是在等待signal信号的队列。
-
每个线程会存在上述两个队列中的一个,lock与unlock对应在AQS队列,signal与await对应条件队列,线程节点在他们之间反复横跳。
这里着重说明一下,接下来的源码学习部分,我们会将两个队列进行区分,涉及到同步队列和阻塞队列的描述,意味着是AQS的同步队列,而条件队列指的是Condition队列,望读者知晓。
这里我们针对上面的demo来分析一下会更好理解一些:
为了简化,接下来我将用D表示等待线程,用T表示通知线程。
-
【D】先调用
lock.lock()
方法,此时无竞争,【D】被加入到AQS同步队列中。 -
【D】调用
condition.await()
方法,此时【D】被构建为等待节点并加入到condition对应的条件等待队列中,并从AQS同步队列中移除。 -
【D】陷入等待之后,【T】启动,由于AQS队列中的【D】已经被移除,此时【T】也很快获取到锁,相应的,【T】也被加入到AQS同步队列中。
-
【T】接着调用
condition.signal()
方法,这时condition对应的条件队列中只有一个节点【D】,于是【D】被取出,并被再次加入AQS的等待队列中。此时【D】并没有被唤醒,只是单纯换了个位置。 -
接着【T】执行
lock.unlock()
,释放锁锁之后,会唤醒AQS队列中的【D】,此时【D】真正被唤醒且执行。
OK,lock -> await -> signal -> unlock
这一套流程相信已经大概能够理解,接下来我们试着看看源码吧。
回顾AQS中的Node
我们这里再简单回顾一下AQS中Node类与Condition相关的字段:
// 记录当前线程的等待状态,
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// node存储的线程
版权声明:本文标题:深入详解Condition条件队列、signal和await 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1727948993a1139246.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论