在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

  Java中线程通信协作的最常见的两种方式:

  一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

  二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

  线程间直接的数据交换:

  三.通过管道进行线程间通信:1)字节流;2)字符流   

一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

wait()、notify()和notifyAll()是Object类中的方法:

 /**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the wait methods
*/
public final native void notify(); /**
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* wait methods.
*/
public final native void notifyAll(); /**
* Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
* <p>
* The current thread must own this object's monitor.
*/
public final native void wait(long timeout) throws InterruptedException;

从这三个方法的文字描述可以知道以下几点信息:

  1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)

  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程

  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程

  有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

  上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁)因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)如果当前线程没有这个对象的锁就调用wait()方法,则会抛出IllegalMonitorStateException.

   调用某个对象的wait()方法,相当于让当前线程交出(释放)此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);  

  notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。 同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

  nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。

  这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

  举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了

  上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行

一个生产者一个消费者

一个对象,作为锁(利用该对象的monitor)

 package com.jp.oneone;

 public class ValueObject {

     public static String value = "";

 }

生产者:

 package com.jp.oneone;

 //生产者
public class P extends Thread{ private String lock; public P(String lock) {
super();
this.lock = lock;
} @Override
public void run() {
while (true) {
try {
synchronized (lock) { //当前线程必须获得锁才可以进行下面的操作
if (!ValueObject.value.equals("")) {//如果Value不为空,说明字符串还没被消费,所以调用wait方法,把当前线程(生成线程)阻塞
lock.wait();
}
String value = System.currentTimeMillis() + "_"
+ System.nanoTime();
System.out.println("set的值是" + value);
ValueObject.value = value;//为空的话,则生成
lock.notify();//生成完就唤醒等待该对象锁的线程,(这里只有一个消费者等这个锁,所以就是唤醒的它)
} } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

消费者:

package com.jp.oneone;

//消费者
public class C extends Thread { private String lock; public C(String lock) {
super();
this.lock = lock;
} @Override
public void run() {
while (true) {
try {
synchronized (lock) {
if (ValueObject.value.equals("")) {//如果字符串为空,即被消费完了,所以wait等待。
lock.wait();
}
System.out.println("get的值是" + ValueObject.value);
ValueObject.value = "";
lock.notify();
} } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

测试:

package com.jp.oneone;

public class Run {

    public static void main(String[] args) {

        String lock = new String("");
P p = new P(lock);
C r = new C(lock); p.start();
r.start();
} }

运行结果

本例是1个生产者1个消费者进行数据的交互。

多个生产者多个消费者

实现和上面1对1基本一样,只是在测试代码中,多new几个生产者,几个消费者。

只需注意一个问题:假死

问题描述:所有线程都被wait,这个项目就停止运行了。

问题原因:代码中使用wait/notify进行通信,不能保证notify唤醒的是异类(生产者唤醒消费者还是生产者),比如生产者唤醒生产者,消费者唤醒消费者,就可能导致都在等待的状态。

问题解决:其实很简单,就是唤醒的时候同类异类都唤醒,把notify()改为natifyAll()就解决了。

二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

  Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  Conditon中的await()对应Object的wait();

  Condition中的signal()对应Object的notify();

  Condition中的signalAll()对应Object的notifyAll()。

一个生产者一个消费者

这个例子用了《Java多线程编程核心技术》中的方式,把生产者和消费者的方法写到一个类中,与生成线程和消费线程分开,感觉更高大上,当然上面的例子也可以写成这种方式。

含有生成者消费者的类

 package oneone;

 import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class MyService { private ReentrantLock lock = new ReentrantLock();//拿到可重入锁,相当于synchronized的作用
private Condition condition = lock.newCondition();//调用await和signal方法的对象,相当于Object对象(任意对象)的的wait和notify方法
private boolean hasValue = false; //生产者
public void set() {
try {
lock.lock();//获得锁
while (hasValue == true) {
condition.await(); //没被消费则阻塞该生产线程,当然也释放了锁,进入等锁的队列
}
System.out.println("打印★");
hasValue = true;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} //消费者
public void get() {
try {
lock.lock();
while (hasValue == false) {
condition.await();
}
System.out.println("打印☆");
hasValue = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} }

对应生成者的线程类

package oneone;

public class MyThreadA extends Thread {

    private MyService myService;

    public MyThreadA(MyService myService) {
super();
this.myService = myService;
} @Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService.set();
}
} }

对应消费者的线程类

package oneone;

public class MyThreadB extends Thread {

    private MyService myService;

    public MyThreadB(MyService myService) {
super();
this.myService = myService;
} @Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService.get();
}
} }

测试类

package oneone;

public class Run {

    public static void main(String[] args) throws InterruptedException {
MyService myService = new MyService(); MyThreadA a = new MyThreadA(myService);
a.start(); MyThreadB b = new MyThreadB(myService);
b.start(); }
}

运行结果

三.通过管道进行线程间通信:1)字节流;2)字符流

Java中有各种各样的输入、输出流(Stream),其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。

一个线程发送数据到输出管道,另一个线程从输入管道读数据。

以字节流举例:

写数据的类,把数据写入管道输出流

package pipeInputOutput;

import java.io.IOException;
import java.io.PipedOutputStream; public class WriteData { public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

读数据的类,从管道输入流读数据

package pipeInputOutput;

import java.io.IOException;
import java.io.PipedInputStream; public class ReadData { public void readMethod(PipedInputStream input) {
try {
System.out.println("read :");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

写数据的线程类

package pipeInputOutput;

import java.io.PipedOutputStream;

public class ThreadWrite extends Thread {

    private WriteData write;
private PipedOutputStream out; public ThreadWrite(WriteData write, PipedOutputStream out) {
super();
this.write = write;
this.out = out;
} @Override
public void run() {
write.writeMethod(out);
} }

读数据的线程类

package pipeInputOutput;

import java.io.PipedInputStream;

public class ThreadRead extends Thread {

    private ReadData read;
private PipedInputStream input; public ThreadRead(ReadData read, PipedInputStream input) {
super();
this.read = read;
this.input = input;
} @Override
public void run() {
read.readMethod(input);
}
}

测试类:

package pipeInputOutput;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream; public class Run { public static void main(String[] args) { try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData(); PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(); //将两个Stream之间产生通信链接,这样才能将数据进行输入输出,下面两种方式都可以,其一即可
//inputStream.connect(outputStream);
outputStream.connect(inputStream); //开启读线程
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start(); Thread.sleep(2000); //开启写线程
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start(); } catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} } }

运行结果

read  :
write :
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300

《Java多线程编程核心技术》

http://www.cnblogs.com/dolphin0520/p/3920385.html

java并发之线程间通信协作的更多相关文章

  1. Java并发之线程间的协作

    上篇文章我们介绍了synchronized关键字,使用它可以有效的解决我们多线程所带来的一些常见问题.例如:竞态条件,内存可见性等.并且,我们也说明了该关键字主要是一个加锁和释放锁的集成,所有为能获得 ...

  2. java并发之线程间通信

    1.volatile 关键字 java 支持多个线程同时访问一个对象或对象的成员变量,而每个线程拥有这个变量的拷贝,虽然对象或成员变量分配的内存在共享内存,但每个执行的线程可以拥有一份拷贝,可以提高程 ...

  3. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

  4. Java并发编程:线程间通信wait、notify

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

  5. Java多线程基础——线程间通信

    在使用多线程的时候,经常需要多个线程进行协作来完成一件事情.在前面两章分析了Java多线程的基本使用以及利用synchronized来实现多个线程同步调用方法或者执行代码块.但上面两章的内容涉及到的例 ...

  6. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

  7. Java多线程:线程间通信之volatile与sychronized

    由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...

  8. Java多线程:线程间通信之Lock

    Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...

  9. Java 如何实现线程间通信?(notify、join、CountdownLatch、CyclicBarrier、FutureTask、Callable )

    转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247486499&idx=1&sn=d3f2d6959df ...

随机推荐

  1. cocos2d-x的gitignore配置

    # Ignore thumbnails created by windows Thumbs.db # Ignore files build by Visual Studio *.obj *.exe * ...

  2. 关于move_uploaded_file()出错的问题

    move_upload0ed_file()函数返回參数较少.可是引起出错的原因却有非常多,所以对于刚開始学习的人难免会遇到问题. 出错原因大概有下面三点: 1.假设检測到文件不是来自post上传.这个 ...

  3. Linux USB 驱动开发(一)—— USB设备基础概念【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50984074 在终端用户看来,USB设备为主机提供了多种多样的附加功能,如文件传输,声音 ...

  4. nyoj--127--星际之门(一)(生成树的数量)

    星际之门(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描述 公元3000年,子虚帝国统领着N个星系,原先它们是靠近光束飞船来进行旅行的,近来,X博士发明了星际之门,它 ...

  5. Spring版本功能变更&Spring4.x的新特性

    有朋友想知道Spring不同版本都有哪些功能变更,说直接在百度搜索找到的结果都不是想要的,其实还是关键词不对,找Spring不同版本的新特性就能获得更好的结果.其实在Spring工程github的wi ...

  6. 通过ip获取地址

    <?php /** * IP 地理位置查询类 * * @author 马秉尧 * @version 1.5 * @copyright 2005 CoolCode.CN */ class IpLo ...

  7. js获取验证码 秒表效果(原创)

    <script src="http://code.jquery.com/jquery-latest.js"></script> <input type ...

  8. .net core发布到IIS后502.5错误

    net core 在win7系统发布后,出现在502.5错误. 打开“开始”菜单,搜索“事件查看器”,然后选择“事件查看器”应用. 在“事件查看器”中,打开“Windows 日志”节点. 选择“应用程 ...

  9. html中map标签和area标签的应用

    map标签的用途:是与img标签绑定使用的,常被用来赋予给客户端图像某处区域特殊的含义,点击该区域可跳转到新的文档. 因为map标签是与img标签绑定使用的,所以我们需要给map标签添加ID和name ...

  10. Java 实时论坛 - Sym 1.3.0 发布

    简介 Sym 是一个用 Java 写的实时论坛,欢迎来体验! 初衷 Sym 的诞生是有如下几点原因: 我们想实现一种新的网络社区体验,独立博客+社区互动 大多数论坛用户体验不够现代化,想做一个和聊 Q ...