线程间通信

线程之间除了同步互斥,还要考虑通信。在Java5之前我们的通信方式为:wait 和 notify。Condition的优势是支持多路等待,即可以定义多个Condition,每个condition控制线程的一条执行通路。传统方式只能是一路等待

Condition提供不同于Object 监视器方法的行为和语义,如受保证的通知排序,或者在执行通知时不需要保持一个锁。

Condition接口

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

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();
}

说明:

await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

awaitUninterruptibly()方法与await()方法基本相同,但awaitUninterruptibly()方法不会在等待过程中响应中断。

singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法类似。

condition.await()方法必须在lock.lock()与lock.unlock()方法之间调用。

获取Condition

Condition实例实质上被绑定到一个锁上。一个锁内部可以有多个Condition,即有多路等待和通知。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

Condition newCondition() 返回用来与当前Lock实例一起使用的Condition 实例。

类似于 object.wait()和object.notify()的功能。object.wait()与object.notify()需要结合synchronized使用。Condition需要结合ReentrantLock使用。

"虚假唤醒"

所谓"虚假唤醒",即其他地方的代码触发了condition.signal(),唤醒condition上等待的线程。但被唤醒的线程仍然不满足执行条件。

condition通常与条件语句一起使用:

if(!条件){
condition.await(); //不满足条件,当前线程等待;
}

更好的方法是使用while:

while(!条件){
condition.await(); //不满足条件,当前线程等待;
}

在等待Condition时,允许发生"虚假唤醒",这通常作为对基础平台语义的让步。若使用"if(!条件)"则被"虚假唤醒"的线程可能继续执行。所以"while(!条件)"可以防止"虚假唤醒"。建议总是假定这些"虚假唤醒"可能发生,因此总是在一个循环中等待。

例:缓冲队列的实现。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; class BoundedBuffer {
final Lock lock = new ReentrantLock();// 锁对象
final Condition notFull = lock.newCondition(); //写线程条件
final Condition notEmpty = lock.newCondition();//读线程条件
final Object[] items = new Object[100];// 初始化一个长度为100的队列
int putptr/* 写索引 */, takeptr/* 读索引 */, count/* 队列中存在的数据个数 */; public void put(Object x) throws InterruptedException {
lock.lock(); //获取锁
try {
while (count == items.length)
notFull.await();// 当计数器count等于队列的长度时,不能再插入,因此等待。阻塞写线程。
items[putptr] = x;//赋值
putptr++; if (putptr == items.length)
putptr = 0;// 若写索引写到队列的最后一个位置了,将putptr置为0。
count++; // 每放入一个对象就将计数器加1。
notEmpty.signal(); // 一旦插入就唤醒取数据线程。
} finally {
lock.unlock(); // 最后释放锁
}
} public Object take() throws InterruptedException {
lock.lock(); // 获取锁
try {
while (count == 0)
notEmpty.await(); // 如果计数器等于0则等待,即阻塞读线程。
Object x = items[takeptr]; // 取值
takeptr++;
if (takeptr == items.length)
takeptr = 0; //若读锁应读到了队列的最后一个位置了,则读锁应置为0;即当takeptr达到队列长度时,从零开始取
count++; // 每取一个将计数器减1。
notFull.signal(); //枚取走一个就唤醒存线程。
return x;
} finally {
lock.unlock();// 释放锁
}
} }

此即Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

例:经典问题:三个线程依次打印ABC,代码示例如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; class Business {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private String type = "A"; //内部状态 /*
* 方法的基本要求为:
* 1、该方法必须为原子的。
* 2、当前状态必须满足条件。若不满足,则等待;满足,则执行业务代码。
* 3、业务执行完毕后,修改状态,并唤醒指定条件下的线程。
*/
public void printA() {
lock.lock(); //锁,保证了线程安全。
try {
while (type != "A") { //type不为A,
try {
conditionA.await(); //将当前线程阻塞于conditionA对象上,将被阻塞。
} catch (InterruptedException e) {
e.printStackTrace();
}
} //type为A,则执行。
System.out.println(Thread.currentThread().getName() + " 正在打印A");
type = "B"; //将type设置为B。
conditionB.signal(); //唤醒在等待conditionB对象上的一个线程。将信号传递出去。
} finally {
lock.unlock(); //解锁
}
} public void printB() {
lock.lock(); //锁
try {
while (type != "B") { //type不为B,
try {
conditionB.await(); //将当前线程阻塞于conditionB对象上,将被阻塞。
} catch (InterruptedException e) {
e.printStackTrace();
}
} //type为B,则执行。
System.out.println(Thread.currentThread().getName() + " 正在打印B");
type = "C"; //将type设置为C。
conditionC.signal(); //唤醒在等待conditionC对象上的一个线程。将信号传递出去。
} finally {
lock.unlock(); //解锁
}
} public void printC() {
lock.lock(); //锁
try {
while (type != "C") {
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + " 正在打印C");
type = "A";
conditionA.signal();
} finally {
lock.unlock(); //解锁
}
}
} public class Test{ public static void main(String[] args) {
final Business business = new Business();//业务对象。 //线程1号,打印10次A。
Thread ta = new Thread(new Runnable() { @Override
public void run() {
for(int i=0;i<10;i++){
business.printA();
}
}
}); //线程2号,打印10次B。
Thread tb = new Thread(new Runnable() { @Override
public void run() {
for(int i=0;i<10;i++){
business.printB();
}
}
}); //线程3号,打印10次C。
Thread tc = new Thread(new Runnable() { @Override
public void run() {
for(int i=0;i<10;i++){
business.printC();
}
}
}); //执行3条线程。
ta.start();
tb.start();
tc.start();
} }

执行代码,控制台依次显示了A、B、C,10次。可以看到3条线程之间共享Business类中的资源type,且3条线程之间进行了有效的协调。

Java并发——使用Condition线程间通信的更多相关文章

  1. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  2. 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  3. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  4. Java笔记(二十)……线程间通信

    概述 当需要多线程配合完成一项任务时,往往需要用到线程间通信,以确保任务的稳步快速运行 相关语句 wait():挂起线程,释放锁,相当于自动放弃了执行权限 notify():唤醒wait等待队列里的第 ...

  5. Java多线程编程(6)--线程间通信(下)

      因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式.   在实际的软件开发过程中,经常会碰到如下场景 ...

  6. Java多线程编程核心技术---线程间通信(二)

    通过管道进行线程间通信:字节流 Java提供了各种各样的输入/输出流Stream可以很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送 ...

  7. Java多线程编程核心技术---线程间通信(一)

    线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一.线程间通信可以使系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各 ...

  8. Java 里如何实现线程间通信(转载)

    出处:http://www.importnew.com/26850.html 正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程 ...

  9. Java 里如何实现线程间通信

    正常情况下,每个子线程完成各自的任务就可以结束了.不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了. 本文涉及到的知识点:thread.join(), object.w ...

随机推荐

  1. USACO4.13Fence Loops(无向图最小环)

    最近脑子有点乱 老是不想清楚就啪啪的敲 敲完之后一看 咦..样例都过不去 仔细一想 这样不对啊 刚开始就写了一SPFA 最后发现边跟点的关系没处理好 删了..写dfs..还是没转化好 开始搜解题方法 ...

  2. POJ 1062 昂贵的聘礼 解题报告

    本题不难,但是笔者贡献了30多次Submit……就像Discuss讨论的一样,细节决定成败,WA了肯定有理由. 贴代码,Dijkstra+优先队列. #include <cstdio> # ...

  3. apache虚拟主机的设置

    方法一: 首先打开apache中conf下的http.conf文件打开虚拟主机的注释:如下去掉第二行前面的#即可 # Virtual hosts# Include conf/extra/httpd-v ...

  4. 【转】linux /usr/bin/ld cannot find 解决

    原文网址:http://blog.csdn.net/mzwang123/article/details/6702889 问题:在linux环境编译应用程式或lib的source code时常常会出现如 ...

  5. 【转】git使用教程

    Git使用教程 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是 ...

  6. hbase单机环境的搭建和完全分布式Hbase集群安装配置

    HBase 是一个开源的非关系(NoSQL)的可伸缩性分布式数据库.它是面向列的,并适合于存储超大型松散数据.HBase适合于实时,随机对Big数据进行读写操作的业务环境. @hbase单机环境的搭建 ...

  7. c++学习_1

    最近重新捧起了<Effective c++>,其中味道又有不同,这里记录之... 这篇文章记录一下public继承的知识点... (1)public继承的意义 该继承方式是代表is-a(是 ...

  8. iOS绘图教程 (转,拷贝以记录)

    本文是<Programming iOS5>中Drawing一章的翻译,考虑到主题完整性,在翻译过程中我加入了一些书中没有涉及到的内容.希望本文能够对你有所帮助. 转自:http://www ...

  9. Codeforces Round #341 (Div. 2) ABCDE

    http://www.cnblogs.com/wenruo/p/5176375.html A. Wet Shark and Odd and Even 题意:输入n个数,选择其中任意个数,使和最大且为奇 ...

  10. Task任务