1、线程的协调运行

线程的协调运行有一个经典案例,即生产者和消费者问题。

假设有一个货架,生产者往货架上放货物,消费者从货架上取货物。

为了方便讲解,制定一个规则,生产者每放上一个货物,消费者就得取走一个货物。不允许连续放两次,也不允许连续取两次。

为了实现这个功能,可以使用wait()、notify()和notifyAll()三个方法。注意,这三个方法不属于Thread类,而是属于Object类,且必须由同步监视器对象调用。详细用法如下:

<![if !supportLists]>Ø  <![endif]>对于使用synchronized修饰的同步方法,因为默认实例(this)就是同步监视器对象,所以可以在同步方法中直接调用这三个方法。

<![if !supportLists]>Ø  <![endif]>对于使用synchronized修饰的同步代码块,同步监视器对象是synchronized后括号里的对象,必须使用该对象调用这三个方法

这三个方法的作用如下:

<![if !supportLists]>Ø  <![endif]>wait():使当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程。调用wait()的当前线程会释放对该同步监视器的锁定。

<![if !supportLists]>Ø  <![endif]>notify():唤醒在此同步监视器上的单个线程,如果有多个线程在等待,则随机唤醒一个。被唤醒的线程会在当前线程释放对同步监视器的锁定后开始运行。

<![if !supportLists]>Ø  <![endif]>notifyAll():唤醒在此同步监视器上的所有线程,其余和notify()相同。

货架代码如下:

publicclass Shelf {

//是否有货物的标志,true:有;false:没有

privatebooleanflag;

//放入货物的方法

publicsynchronizedvoid product(){

//flag为假时表示没有货物,此时可放入货物

if(!flag){

flag = true;

System.out.println(Thread.currentThread().getName()+"放入了货物。");

notifyAll();

}else{

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

//取走货物的方法

publicsynchronizedvoid custom(){

//flag为真时表示有货物,此时可取走货物

if (flag) {

flag = false;

System.out.println(Thread.currentThread().getName()+"取走了货物。");

notifyAll();

}else{

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

生产者线程代码如下:

publicclass ProducerThread implements Runnable{

private Shelf shelf;

public ProducerThread(Shelf shelf){

this.shelf = shelf;

}

@Override

publicvoid run() {

//放十次货物

for (int i = 0; i < 10; i++) {

shelf.product();

}

}

}

消费者线程代码如下:

publicclass CustomerThread implements Runnable{

private Shelf shelf;

public CustomerThread(Shelf shelf){

this.shelf = shelf;

}

@Override

publicvoid run() {

//取十次货物

for (int i = 0; i < 10; i++) {

shelf.custom();

}

}

}

测试代码如下:

publicclass TestShelf {

publicstaticvoid main(String[] args) {

Shelf shelf = new Shelf();

ProducerThread producerThread = new ProducerThread(shelf);

CustomerThread customerThread = new CustomerThread(shelf);

new Thread(producerThread,"生产者1").start();

new Thread(producerThread,"生产者2").start();

new Thread(customerThread,"消费者").start();

}

}

运行结果如下:

生产者1放入了货物。

消费者取走了货物。

生产者1放入了货物。

消费者取走了货物。

生产者2放入了货物。

消费者取走了货物。

生产者1放入了货物。

消费者取走了货物。

生产者1放入了货物。

消费者取走了货物。

生产者2放入了货物。

消费者取走了货物。

生产者2放入了货物。

消费者取走了货物。

生产者2放入了货物。

上例中启动了两个生产者一个消费者,可见生产和消费是交替进行的,满足生产者每放上一个货物,消费者就得取走一个货物的规则。

最后程序将阻塞,因为消费者比较少,生产者最后只能一直等消费者来取走货物。同时消费者取货物的次数也小于10次,因为有时取货时没货了。

2、使用条件变量协调运行

如果不使用synchronized,而是使用Lock对象来保证同步,则不能使用wait()、notify()和notifyAll()这三个方法。此时可使用Condition类来实现协作。

Condition实例必须从Lock实例中获得,也有三个方法,如下:

<![if !supportLists]>Ø  <![endif]>await():和wait()类似,使当前线程等待,直到被唤醒。相比wait(),await()有更多的变体,这个以后再说。

<![if !supportLists]>Ø  <![endif]>signal():和notify()类似,唤醒当前Lock对象上的单条线程。

<![if !supportLists]>Ø  <![endif]>signalAll()和notifyAll()类似,唤醒当前Lock对象上的所有线程。

生产者线程、消费者线程、测试代码和测试结果的代码跟上一节完全一样,这里只放出货架的代码。

货架的代码:

publicclass Shelf {

//是否有货物的标志,true:有;false:没有

privatebooleanflag;

//定义Lock对象

privatefinal Lock lock = new ReentrantLock();

//获取Lock对象对应的Condition对象

privatefinal Condition condition= lock.newCondition();

//放入货物的方法

publicvoid product(){

lock.lock();

try {

//flag为假时表示没有货物,此时可放入货物

if(!flag){

flag = true;

System.out.println(Thread.currentThread().getName()+"放入了货物。");

condition.signalAll();

}else{

condition.await();

}

} catch (Exception e) {

e.printStackTrace();

}finally{

lock.unlock();

}

}

//取走货物的方法

publicsynchronizedvoid custom(){

lock.lock();

try {

//flag为真时表示有货物,此时可取走货物

if (flag) {

flag = false;

System.out.println(Thread.currentThread().getName()+"取走了货物。");

condition.signalAll();

}else{

condition.await();

}

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

}finally{

lock.unlock();

}

}

}

3、使用管道流通信

线程之间使用管道流通信并不是最好的方式,通常推荐使用共享资源的方式交换信息。所以本节内容了解一下就好。

管道流有三种形式,分别是管道字节流(PipedInputStream、PipedOutputStream)、管道字符流(PipedReader、PipedWriter)和新IO的管道Channel(Pipe.SinkChannel、Pipe.SourceChannel)。

使用步骤如下:

<![if !supportLists]>Ø  <![endif]>创建输入管道输入流和管道输出流对象。

<![if !supportLists]>Ø  <![endif]>使用管道输入流或管道输出流的connect方法把输入流和输出流连接起来。

<![if !supportLists]>Ø  <![endif]>将管道输入流、管道输出流分别传入两个线程

<![if !supportLists]>Ø  <![endif]>线程使用各自的管道流通信

代码就不贴了

java多线程回顾4:线程通信的更多相关文章

  1. java多线程 -- Condition 控制线程通信

    Api文档如此定义: Condition 将 Object 监视器方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对 ...

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

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

  3. Java多线程——进程和线程

    Java多线程——进程和线程 摘要:本文主要解释在Java这门编程语言中,什么是进程,什么是线程,以及二者之间的关系. 部分内容来自以下博客: https://www.cnblogs.com/dolp ...

  4. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  5. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  6. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  7. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  8. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  9. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  10. 转:java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例

    java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例 1.CountDownLatch:一个同步工具类,它允许一个或多个线程一 ...

随机推荐

  1. yum安装出现No package vim available解决办法

    [root@iZwz96wgquf8g6okusxr0uZ yum.repos.d]# yum install yum-downloadLoaded plugins: fastestmirrorLoa ...

  2. 【Labview入门】将输入度数转换为3位精度弧度值

    Labview版本2015 程序如下: 可以右键输出控件选择属性来调整输出的小数位数: 运行结果:

  3. 8.Linux用户管理(上)

    1.什么是用户? 能正常登陆系统的都算用户 windows系统和linux系统的用户有什么区别? 本质上没有区别, linux支持多个用户同一时刻登陆系统, 互相之间不影响 而windows只允许同一 ...

  4. static self 区别与总结

    <?php /** * static self 区别与总结 * 总结: * 1.在 PHP 里,在没有继承时候,你用self::class 和 static::class是一样的,都是获取当前类 ...

  5. Juc1024小半年总结-面试篇

    大家好,我叫Juc 这大概是我时隔2年度多 第一次以分享的形式发的第一篇公众号 今天是2019年10月26 本想在10月24就分享一下 可惜前面两天时间太忙... 很凑巧,今天我出来工作刚好满4个月, ...

  6. BeetleX服务网关之服务发现与泛域名路由

    在新版本的服务网关中提供了服务发现和泛域名路由解决功能,服务发现可以在无须配置的情况下实现服务自动注册到网关中解脱对服务配置的繁琐工作:而泛域名路由则可以针对不同的域名制定不同的负载规则. 使用con ...

  7. 使用maven替换项目依赖中的字节码

    问题描述 我们偶尔会发现一些开源项目的问题,或者出于其他原因,想在某个dependency的代码中加几行或者删除几行来达到目的. 我这里遇到一个dubbo 2.7.3和open feign冲突的问题 ...

  8. 面试题-javascript-面向对象编程

    笔者在某次笔试中遇到这个题:印象很深. function ClassA() { var value=4; this.getValue= function() { return value; } thi ...

  9. element ui实现手动上传文件,且只能上传单个文件,并能覆盖上传。

    element ui提供了成熟的组件场景,但实际工作中难免会遇到认(sha)真(diao)的产品.比如,最近遇到的,要求实现手动上传特定格式文件(用户点击“上传文件”按钮,确定之后,只是单纯选择了文件 ...

  10. Android 正则表达式,Pattern,Matcher基本使用

    Pattern类:      Pattern的创建:      Pattern pattern =Pattern.complie(String regex)     参数说明:regex:是一个正则表 ...