并发任务间交换数据

Java 9并发编程指南 目录

Java并发API提供了一种机制,允许两个并发任务之间数据交换。具体的说,就是Exchanger类定义两个线程之间有一个同步点。当两个线程到达这个同步点时,交换各自线程的数据结构。

这个类对于类似生产者-消费者问题的情况非常有帮助,着这个经典的并发问题中,有数据通用缓存,生产者和消费者的数据。因为Exchanger类只同步两个线程,所以在生产者-消费者问题中,一个可以用作生产者,另一个用作消费者。

在本节中,学习如何使用Exchanger类解决包含生产者和消费者的生产者-消费者问题。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 首先,开始首先生产者。创建名为Produce的类,并实现Runnable接口:

    public class Producer implements Runnable {
    
  2. 定义名为buffer的List域,作为生产者与消费者交换的数据结构:

    	private List<String> buffer;
    
  3. 定义名为exchanger的Exchanger<List>域,用来同步生产者和消费者的交换对象:

    	private final Exchanger<List<String>> exchanger;
    
  4. 实现类构造函数,初始化两个属性:

    	public Producer(List<String> buffer, Exchanger<List<String>> exchanger) {
    		this.buffer = buffer;
    		this.exchanger = exchanger;
    	}
    
  5. 实现run()方法,在内部实现10次交换循环:

    	@Override
    	public void run() {
    		for (int cycle = 1 ; cycle <= 10 ; cycle ++) {
    			System.out.printf("Producer : Cycle %d\n", cycle);
    
  6. 每次循环中,添加10条字符串到缓存中:

    			for (int j = 0 ; j < 10 ; j ++) {
    				String message = "Event " + (((cycle-1) * 10 ) + j);
    				System.out.printf("Producer : %s\n", message);
    				buffer.add(message);
    			}
    
  7. 调用exchange()方法与消费者交换数据,由于此方法会抛出InterruptedException异常,需要添加代码进行处理:

    			try {
    				buffer = exchanger.exchange(buffer);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.printf("Producer : " + buffer.size() + "\n");	
    		}
    
  8. 现在实现消费者。创建名为Consumer的类,并实现Runnable接口:

    public class Consumer implements Runnable { 
    
  9. 定义名为buffer的List域,作为生产者与消费者交换的数据结构:

    	private List<String> buffer;
    
  10. 定义名为exchanger的Exchanger<List>域,用来同步生产者和消费者的交换对象:

    	private final Exchanger<List<String>> exchanger;
    
  11. 实现类构造函数,初始化两个属性:

    	public Consumer(List<String> buffer, Exchanger<List<String>> exchanger) {
    		this.buffer = buffer;
    		this.exchanger = exchanger;
    	}
    
  12. 实现run()方法,在内部实现10次交换循环:

    	@Override
    	public void run() {
    		for (int cycle = 1 ; cycle <= 10 ; cycle ++) {
    			System.out.printf("Consumer : Cycle %d\n", cycle);
    
  13. 每次循环中,开始调用exchange()方法与生产者同步,消费者需要数据进行消费。由于此方法会抛出InterruptedException异常,需要添加代码进行处理:

    			try {
    				buffer = exchanger.exchange(buffer);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    
  14. 输出生产者发送到缓存中的10条字符串到控制台,然后删除直到缓存为空:

    			System.out.printf("Consumer : " + buffer.size() + "\n");
    			for (int j = 0 ; j < 10 ; j ++) {
    				String message = buffer.get(0);
    				System.out.printf("Consumer : %s\n", message);
    				buffer.remove(0);
    			}
    		}
    
  15. 现在,实现范例的主类,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  16. 创建两个缓存,分别被生产者和消费者使用:

    		List<String> buffer1 = new ArrayList<>();
    		List<String> buffer2 = new ArrayList<>();
    
  17. 创建Exchanger对象,用来同步生产者和消费者:

    		Exchanger<List<String>> exchanger = new Exchanger<>();
    
  18. 创建Producer和Consumer对象:

    		Producer producer = new Producer(buffer1, exchanger);
    		Consumer consumer = new Consumer(buffer2, exchanger);
    
  19. 创建线程执行生产者和消费者,启动线程:

    		Thread threadProducer = new Thread(producer);
    		Thread threadConsumer = new Thread(consumer);	
    
    		threadProducer.start();
    		threadConsumer.start();
    

工作原理

消费者在空缓存中开始执行,并且调用Exchanger与生产者同步。它需要数据来消费,生产者在空缓存中开始执行,创建10条字符串存储到缓存中,然后使用Exchanger与消费者同步。

在同步点处,两个线程(生产者与消费者)均在改变数据结构的Exchanger中。所以当消费者从exchange()方法中返回时,缓存中有10条字符串。当生产者从exchange()方法中返回时,缓存为空等待写入。这个操作过程重复10次。

在执行本范例时,可以看到生产者与消费者如何并发地工作,以及每一步中两个对象如何交换缓存。由于此过程用到其他同步功能,所以第一个调用exchange()方法的线程将休眠,直到另一个线程到达。

扩展学习

在Exchanger类中,exchange方法还有另一种形式:

exchange(V data, long time ,TimeUnit unit)

其中,V是在Phaser定义中的类型作为参数(本范例中是List)。线程将休眠直到另一个线程到达或者已过指定时间,才会被中断。这种情况下,会抛出TimeoutException异常。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值