Java并发API提供了一种机制,允许两个并发任务之间数据交换。具体的说,就是Exchanger类定义两个线程之间有一个同步点。当两个线程到达这个同步点时,交换各自线程的数据结构。
这个类对于类似生产者-消费者问题的情况非常有帮助,着这个经典的并发问题中,有数据通用缓存,生产者和消费者的数据。因为Exchanger类只同步两个线程,所以在生产者-消费者问题中,一个可以用作生产者,另一个用作消费者。
在本节中,学习如何使用Exchanger类解决包含生产者和消费者的生产者-消费者问题。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
首先,开始首先生产者。创建名为Produce的类,并实现Runnable接口:
public class Producer implements Runnable {
-
定义名为buffer的List域,作为生产者与消费者交换的数据结构:
private List<String> buffer;
-
定义名为exchanger的Exchanger<List>域,用来同步生产者和消费者的交换对象:
private final Exchanger<List<String>> exchanger;
-
实现类构造函数,初始化两个属性:
public Producer(List<String> buffer, Exchanger<List<String>> exchanger) { this.buffer = buffer; this.exchanger = exchanger; }
-
实现run()方法,在内部实现10次交换循环:
@Override public void run() { for (int cycle = 1 ; cycle <= 10 ; cycle ++) { System.out.printf("Producer : Cycle %d\n", cycle);
-
每次循环中,添加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); }
-
调用exchange()方法与消费者交换数据,由于此方法会抛出InterruptedException异常,需要添加代码进行处理:
try { buffer = exchanger.exchange(buffer); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Producer : " + buffer.size() + "\n"); }
-
现在实现消费者。创建名为Consumer的类,并实现Runnable接口:
public class Consumer implements Runnable {
-
定义名为buffer的List域,作为生产者与消费者交换的数据结构:
private List<String> buffer;
-
定义名为exchanger的Exchanger<List>域,用来同步生产者和消费者的交换对象:
private final Exchanger<List<String>> exchanger;
-
实现类构造函数,初始化两个属性:
public Consumer(List<String> buffer, Exchanger<List<String>> exchanger) { this.buffer = buffer; this.exchanger = exchanger; }
-
实现run()方法,在内部实现10次交换循环:
@Override public void run() { for (int cycle = 1 ; cycle <= 10 ; cycle ++) { System.out.printf("Consumer : Cycle %d\n", cycle);
-
每次循环中,开始调用exchange()方法与生产者同步,消费者需要数据进行消费。由于此方法会抛出InterruptedException异常,需要添加代码进行处理:
try { buffer = exchanger.exchange(buffer); } catch (InterruptedException e) { e.printStackTrace(); }
-
输出生产者发送到缓存中的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); } }
-
现在,实现范例的主类,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
创建两个缓存,分别被生产者和消费者使用:
List<String> buffer1 = new ArrayList<>(); List<String> buffer2 = new ArrayList<>();
-
创建Exchanger对象,用来同步生产者和消费者:
Exchanger<List<String>> exchanger = new Exchanger<>();
-
创建Producer和Consumer对象:
Producer producer = new Producer(buffer1, exchanger); Consumer consumer = new Consumer(buffer2, exchanger);
-
创建线程执行生产者和消费者,启动线程:
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。