[笔记][Java7并发编程实战手冊]3.8 并发任务间的数据交换Exchanger

[笔记][Java7并发编程实战手冊]系列文件夹


简单介绍

  Exchanger 是一个同步辅助类。用于两个并发线程之间在一个同步点进行数据交换。

  同意两个线程在某一个点进行数据交换。


本章exchanger 使用心得总结

  1. 两个线程必须使用同一个Exchanger对象,且仅仅能是两个线程间的数据交换
  2. exchanger.exchange(v)的时候,当前线程会被堵塞,直到还有一个线程运行该方法,同一时候完成数据的交换
  3. 相似这样的数据交换的,生产者线程一定要先生产数据。再交换数据,消费者线程一定要先交换数据,再消费数据,否则会出现少消费数据的现象

演示样例

场景描写叙述:一对一的 生产者和消费者。生产者每次生产5个商品,然后消费者把空的商品容器和生产者交换。

/**
 * Created by zhuqiang on 2015/8/23 0023.
 */
public class Client {
    public static void main(String[] args) {
        Exchanger<ArrayList<String>> exchanger = new Exchanger<ArrayList<String>>();

        new Thread(new Producer(exchanger)).start();
        new Thread(new Consumer(exchanger)).start();

    }
}

// 生产者
class Producer implements Runnable {
    private ArrayList<String> goods = new ArrayList<String>();  //商品容器
    private Exchanger<ArrayList<String>> exchanger = new Exchanger<ArrayList<String>>();

    public Producer(Exchanger<ArrayList<String>> exchanger) {
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) { //生产3次
            System.out.println("------------------------生产者生产第 " + i + "次");
            for (int j = 0; j < 3; j++) { //每次生产3个商品
                String e = (long) (Math.random() * 1000) + "";
                goods.add(e);
                System.out.println("生产了商品:" + e);
            }
            try {
                goods = exchanger.exchange(goods);  //交换数据
                System.out.println("生产者:数据交换完成:获得交换的商品容器大小:" + goods.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable {
    private ArrayList<String> goods = new ArrayList<String>();  //商品容器
    private Exchanger<ArrayList<String>> exchanger = new Exchanger<ArrayList<String>>();

    public Consumer(Exchanger<ArrayList<String>> exchanger) {
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {  //消费3次
            try {
                goods = exchanger.exchange(goods);  //交换数据
                System.out.println("消费者:数据交换完成:获得交换的商品容器大小:" + goods.size());

                // 消费商品
                Iterator<String> it = goods.iterator();
                if (goods.size() > 0) {
                    System.out.println("*********************消费者消费第 " + i + "次");
                    while (it.hasNext()) {
                        String next = it.next();
                        System.out.println("消费了商品:" + next);
                        it.remove();  //移除消费了的商品
                    }
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

某一次运行结果:

------------------------生产者生产第 0次
生产了商品:727
生产了商品:671
生产了商品:345
生产者:数据交换完成:获得交换的商品容器大小:0
------------------------生产者生产第 1次
生产了商品:67
生产了商品:466
生产了商品:207
消费者:数据交换完成:获得交换的商品容器大小:3
*********************消费者消费第 0次
消费了商品:727
消费了商品:671
消费了商品:345
消费者:数据交换完成:获得交换的商品容器大小:3
生产者:数据交换完成:获得交换的商品容器大小:0
------------------------生产者生产第 2次
*********************消费者消费第 1次
消费了商品:67
消费了商品:466
消费了商品:207
生产了商品:38
生产了商品:355
生产了商品:24
生产者:数据交换完成:获得交换的商品容器大小:0
消费者:数据交换完成:获得交换的商品容器大小:3
*********************消费者消费第 2次
消费了商品:38
消费了商品:355
消费了商品:24

结果说明

看上面的结果,能够发现两个线程是并行运行的,仅仅有在交换的时候,交换数据同步点。

是不是认为看着不明所以。这样看

————————生产者生产第 0次

生产了商品:727

第0次生产商品是 727。

***********消费者消费第 0次

消费了商品:727

第0次消费的也是727, 这就说明了。两个线程之间交换数据是我们想要的结果。

假如:我们把 以下的运行顺序调换下,让consumer在前。结果你发现还是先生产后消费。

这个是为什么呢?

    new Thread(new Producer(exchanger)).start();
    new Thread(new Consumer(exchanger)).start();

是由于:

1. 从java内存模型角度来说。上面两行代码没有数据依赖性(在实际运行的时候他们的顺序不一定是谁先谁后)

2. 原因就出如今exchanger.exchange(v)方法。就算是消费者先运行,细致看代码。消费者会先运行exchanger.exchange(v)方法。假设之前没有线程运行该方法,那么消费者则会休眠等待生产者运行该方法。

时间: 2024-12-21 04:06:50

[笔记][Java7并发编程实战手冊]3.8 并发任务间的数据交换Exchanger的相关文章

【Java并发编程实战】—–synchronized

在我们的实际应用其中可能常常会遇到这样一个场景:多个线程读或者.写相同的数据,訪问相同的文件等等.对于这样的情况假设我们不加以控制,是非常easy导致错误的. 在java中,为了解决问题,引入临界区概念.所谓临界区是指一个訪问共用资源的程序片段,而这些共用资源又无法同一时候被多个线程訪问. 在java中为了实现临界区提供了同步机制.当一个线程试图訪问一个临界区时,他将使用一种同步机制来查看是不是已经有其它线程进入临界区. 假设没有则他就能够进入临界区,否则他就会被同步机制挂起,指定进入的线程离开

[笔记][Java7并发编程实战手册]3.2 资源的并发访问控制Semaphore信号量

[笔记][Java7并发编程实战手册]系列目录 简介 本文学习信号量Semaphore机制. Semaphore 本质是一个共享锁 内部维护一个可用的信号集,获取信号量之前需要先申请获取信号数量:用完之后,则需要释放信号量:如果不释放,那么其他等待线程则一直阻塞直到获取信号量或则被中断为止 本人的理解是:互斥锁是同一时间只能一个线程访问,而在这里,是同一时间允许获取到了信号量的线程并发访问,而没有获取到信号量的则必须等待信号量的释放: 将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,

[笔记][Java7并发编程实战手册]4.3 创建固定的线程执行器newFixedThreadPool线程池

[笔记][Java7并发编程实战手册]系列目录 简介 newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程. newCachedThreadPool()创建的线程池的特性是:自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创

[笔记][Java7并发编程实战手册]3.3 资源的多副本并发访问控制Semaphore

[笔记][Java7并发编程实战手册]系列目录 简介 本文继续学习信号量Semaphore机制. 在3.2中其实已经讲解完了,之前对于信号量并发的使用场景不知道,看了本章节才想到一些: 下面就以 租车为列子来讲解并发访问的控制.(示例都很简单或许不符合现实逻辑) 信号量(非二进制信号量)是不保证同步的,需要额外的同步 示例 场景:有一个出租车公司,有三台车,有十个司机,每个司机工作的时间不一致,可以说是司机等待着别人还车后,接着租用汽车. /** * Created by zhuqiang on

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二

[笔记][Java7并发编程实战手册]系列目录 概要 接上一篇文章,练习修改锁的公平性,和在所中使用条件. 修改锁的公平性ReentrantLock /** *构造一个锁对象,默认为非公平锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 根据ReentrantLock的构造可以看出来,默认会构造非公平锁: 公平锁与非公平锁有什么区别 公平锁 :有多个线程并发访

[笔记][Java7并发编程实战手册]系列目录

Java7并发编程实战手册 这一本实战的书籍.本笔记记录是看了该书.并且简化了书中的示例.的一些随笔记录 我觉得能给我更好的感觉.我觉得先看博客中转载的多线程系列 Java多线程系列-目录源码分析和理论.有时候真的觉得好烦躁.可是,没有这些理论实战中又觉得太多的未知. 所以本人觉得.先粗略的过一遍理论和源码分析.再来看学习实战,在写代码的过程中,去回想和联想理论就能更好的把知识串联起来了: 也可以看到本人在记录这些笔记的时候也会引用到博客中转载的多线程系列文章. [笔记][Java7并发编程实战

[笔记][Java7并发编程实战手册]第三章-线程同步辅助类-概要

[笔记][Java7并发编程实战手册]系列目录 有点着急了,没有太注重质量,自己也没有理解透,从本章起,读书和随笔笔记的质量会更好. 第三章 在本章中,我们将学习: 资源的并发访问控制 资源的多副本的并发访问控制 等待多个并发事件的完成 在集合点的同步 并发阶段任务的运行 并发阶段任务中的阶段交换 并发任务间的数据交换 回顾 在第二章中主要学习了以下接口 synchronized关键字 Lock接口以及实现类,如ReentrantLock.ReentrantReadWriteLock中的Read

[笔记][Java7并发编程实战手册]4.4 在执行器中执行任务并返回结果Callable、Future

[笔记][Java7并发编程实战手册]系列目录 简介 执行框架(Executor Framework)的优势之一就是,可以在运行并发任务的时候返回结果.但是需要以下两个类来实现功能: 1. 接口 Callable<V> 返回结果并且可能抛出异常的任务.实现者定义了一个不带任何参数的叫做 call 的方法. Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的.但是 Runnable 不会返回结果,并且无法抛出经过检查的异常. Executors 类

[笔记][Java7并发编程实战手册]4.5 运行多个任务并处理第一个结果ThreadPoolExecutor

[笔记][Java7并发编程实战手册]系列目录 简介 看到这个标题的时候,我也很纳闷,因为没有明白所表达的意思. ok,并发编程比较常见的一个问题是:当采用多个并发任务来解决一个问题的时候,往往只需要关心这个任务的第一个结果,例如:验证一个算法的时候,假如一个执行5个算法,那么最先返回结果的,就是最快的. 在本章将会学习,如何使用ThreadPoolExecutor来实现类似场景: 本章ThreadPoolExecutor使用心得 使用 ThreadPoolExecutor.invokeAny(