前言:对于线程通信,使用synchronized时使用wait、notify和notifyAll来实行线程通信。而使用Lock如何处理线程通信呢?答案就是本片的主角:Condition.

一、Condition

Condition,Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

1,Codition接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait访问的隐式监视器类似。

但提供了更强大的功能,需要指出的是,单个lock可能与多个condition对象关联。为了避免兼容性问题,condition方法的名称与对应的object版本中不一样。

2,在condition对象中,与wait,notify,notifyAll方法对应的分别是await,signal,和signalAll。

3,condition实例实质上是被绑定到了一个锁上,要为特定Lock实例获得condition实例,请使用newCondition()方法。

生产者与消费者程序中,这里通过Lock和condition来联合处理,代替synchronized和wait方法:

/*
* 生产者消费者案例:
*/
public class TestProductorAndConsumerForLock {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer con = new Consumer(clerk); new Thread(pro, "生产者 A").start();
new Thread(con, "消费者 B").start(); new Thread(pro, "生产者 C").start();
new Thread(con, "消费者 D").start();
}
}
class Clerk {
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 进货
public void get() {
// 锁住
lock.lock();
try {
// 为了避免虚假唤醒,应该总是使用在while循环中。
while (product >= 1) {
System.out.println("产品已满!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : "
+ ++product);
condition.signalAll();
} finally {
// unlock放在finally中,可确保一定会锁被释放
lock.unlock();
}
}
// 卖货
public void sale() {
lock.lock();
try {
// 为了避免虚假唤醒,应该总是使用在while循环中。
while (product <= 0) {
System.out.println("缺货!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : "
+ --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
// 生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
// 消费者
class Consumer implements Runnable { private Clerk clerk; public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}

​ 在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

二、Conditon的常用方法

Condition底层模型:

(1)await()

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

源码分析:

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

await方法,其实就是先释放锁(说白了底层就是更改了AQS的state),然后调用LockSupport的park方法park自己,再把自己丢进Condition的等待队列中。这里不要忘记了,释放锁的同时还通知了同步队列中的线程去拿锁哦。

(2)signal()

思想:将在condition的wait queue中等待的线程转移到aqs的队列中。

public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//firstwaiter就是condition的队列中的head
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) &&//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;
}

signal()底层模型:

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

举个例子更好的理解(多个生产者和多个消费者的例子):

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

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

参考链接

本文主要整理自互联网,主要是便于自己复习知识所用,侵权联系删除,以下为原文参考链接!

【1】Java并发编程序列之JUC中Condition线程通讯

【2】JUC-Condition线程通信

【3】juc - Condition源码解读

Condition线程通信(七)的更多相关文章

  1. java 多线程 day13 condition 线程通信

    /** * Created by chengtao on 17/12/5. * Condition 类似 wait和notify,解决线程间的同步问题 */ import java.util.conc ...

  2. 通过一道面试题了解Condition线程通信

    Condition Condition接口描述了可能会与锁有关联的条件变量.这些变量在用法与使用Object.wait访问的隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个Lock可能与多 ...

  3. Condition线程通信_生产者消费者案例

    ①Condition 接口描述了可能会与锁有关联的条件变量. 这些变量在用 法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的 功能. 需要特别指出的是,单个 Lock 可能与 ...

  4. JAVA基础知识之多线程——线程通信

    传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...

  5. Java核心知识点学习----使用Condition控制线程通信

    一.需求 实现线程间的通信,主线程循环3次后,子线程2循环2次,子线程3循环3次,然后主线程接着循环3次,如此循环3次. 即:A->B->C---A->B->C---A-> ...

  6. 多线程之线程通信条件Condition

    Condition是Locks锁下的还有一种线程通信之间唤醒.堵塞的实现.它以下的await,和signal可以实现Object下的wait,notify和notifyAll的所有功能,除此之外改监视 ...

  7. Condition控制线程通信

    Condition控制线程通信 一.前言 java.util.concurrent.locks.Condition 接口描述了可能会与锁有关联的条件变量.这些变量在用法上与使用Object.wait ...

  8. 6.显示锁Lock 和 线程通信Condition

    显示锁 Lock 一.用于解决多线程 安全问题的方式: synchronized:   1.同步代码块      2.同步方法 jdk1.5 后:第三种:同步锁Lock  (注意:同步(synchro ...

  9. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

随机推荐

  1. mysql 查看当前使用的配置文件my.cnf的方法

    my.cnf是mysql启动时加载的配置文件,一般会放在mysql的安装目录中,用户也可以放在其他目录加载. 安装mysql后,系统中会有多个my.cnf文件,有些是用于测试的. 使用locate m ...

  2. item 23: 理解std::move和std::forward

    本文翻译自<effective modern C++>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 根据std::move和std::forward不 ...

  3. BZOJ4361 isn 树状数组、DP、容斥

    传送门 不考虑成为非降序列后停止的限制,那么答案显然是\(\sum\limits_{i=1}^N cnt_i \times (N-i)!\),其中\(cnt_i\)表示长度为\(i\)的非降序列数量 ...

  4. SpringMVC处理跨域请求时的一个注意事项

        由于公司对SpingMVC框架里面的东西进行了扩展,在配置SpringMVC时没有使用<mvc:annotation-driven>这个标签.而且是自己手动来配置HandlerMa ...

  5. UIToolBar - 官方文档

    继承关系:UIToolBar -> UIView -> UIResponder -> NSObject. toolBar是一个工具栏,用于显示一个或多个按钮.其按钮叫做toolBar ...

  6. Java使用Future设置方法超时

    1.使用线程包 java.util.concurrent.Future 2.Future代表一个异步计算的结果. 它提供了方法来检查是否计算已经完成,还是正在计算而处于等待状态,并且也提供了获取计算结 ...

  7. 开源工具 DotnetRSA 快速生成和转换RSA秘钥

    一.简介 DotnetRSA 是一个利用 .NET Core 2.1 开发的 .NET Global Tool,是可以想npm全局安装一样,安装在你的系统中,只需敲一行命令便可以快速生成RSA加密算法 ...

  8. Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简述及技术选型介绍

    作者:13GitHub:https://github.com/ZHENFENG13版权声明:本文为原创文章,未经允许不得转载. 萌芽阶段 很久之前就开始打算整理一下自己的技术博客了,由于各种原因(借口 ...

  9. Windows10上使用Linux子系统(WSL)

    Linux的Windows子系统让开发人员可以直接在Windows上运行Linux环境(包括大多数命令行工具,实用程序和应用程序),而无需建立在虚拟机的开销之上,整个系统共200多M,但包含了你能用到 ...

  10. Intellij Idea 教程

    Intellij Idea 教程 [涵盖安装.配置.常见问题&技巧.Maven.Git.Tomcat.快捷键.项目配置等] 目录 入门............................. ...