Exchanger是自jdk1.5起开始提供的工具套件,一般用于两个工作线程之间交换数据。在本文中我将采取由浅入深的方式来介绍分析这个工具类。
首先我们来看看官方的api文档中的叙述:
A synchronization point at which threads can pair and swap elements within pairs. Each thread presents some object on entry to the exchange method, matches with a partner thread, and receives its partner’s object on return. An Exchanger may be viewed as a bidirectional form of a SynchronousQueue. Exchangers may be useful in applications such as genetic algorithms and pipeline designs.
在以上的描述中,有几个要点:
- 此类提供对外的操作是同步的;
- 用于成对出现的线程之间交换数据;
- 可以视作双向的同步队列;
- 可应用于基因算法、流水线设计等场景。
构造方法:
public Exchanger()
public V exchange(V x) throws InterruptedException
public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
如果两个线程有一个没有到达exchange方法,则会一直等待,如果担心有特殊情况发生,避免一直等待,可以使用exchange(V data, long time, TimeUnit unit)设置最大等待时长。
--- V是声明Phaser的参数种类(例子里是 List)。 此线程会休眠直到另一个线程到达并中断它,或者特定的时间过去了。TimeUnit类有多种常量:DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, 和 SECONDS。
从官方的javadoc可以知道
,如果它的伙伴线程此前已经调用了此方法,那么它的伙伴会被调度唤醒并与之进行对象交换,然后各自返回。如果它的伙伴还没到达交换点,那么当前线程将会被挂起,直至伙伴线程到达——完成交换正常返回;或者当前线程被中断——抛出中断异常;又或者是等候超时——抛出超时异常。
使用例子
下面创建两个生产者线程生产一些序列,然后通过Exchanger进行交换:
package concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Exchanger;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ExchangerTest {
public static void main(String[] args) {
Exchanger<List<Integer>> exchanger = new Exchanger<>();
ThreadPoolExecutor executor=(ThreadPoolExecutor) Executors.newCachedThreadPool();
executor.execute(new Producer(exchanger));
executor.execute(new Producer(exchanger));
}
}
class Producer extends Thread {
List<Integer> list = new ArrayList<>();
Exchanger<List<Integer>> exchanger = null;
public Producer(Exchanger<List<Integer>> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
Random rand = new Random();
int num;
num=rand.nextInt(10000);
list.add(num);
num=rand.nextInt(10000);
list.add(num);
num=rand.nextInt(10000);
list.add(num);
num=rand.nextInt(10000);
list.add(num);
num=rand.nextInt(10000);
list.add(num);
try {
System.out.println(Thread.currentThread().getName()+":交换前:");
print();
list = exchanger.exchange(list);
System.out.println(Thread.currentThread().getName()+":交换后:");
print();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void print(){
for(Integer i:list){
System.out.println(i);
}
}
}
}
执行结果:
pool-1-thread-1:交换前:
185
3669
9943
4364
7101
pool-1-thread-2:交换前:
1091
1364
7341
3755
4966
pool-1-thread-2:交换后:
185
3669
9943
4364
7101
pool-1-thread-1:交换后:
1091
1364
7341
3755
4966
Exchanger的应用场景
1、Exchanger可以用于遗传算法,遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,并使用交叉规则得出2个交配结果。
2、Exchanger也可以用于校对工作。比如我们需要将纸制银流通过人工的方式录入成电子银行流水,为了避免错误,采用AB岗两人进行录入,录入到Excel之后,系统需要加载这两个Excel,并对这两个Excel数据进行校对,看看是否录入的一致。代码如下:
1 public class ExchangerTest { 2 3 private static final Exchanger<String> exgr = new Exchanger<String>(); 4 5 private static ExecutorService threadPool = Executors.newFixedThreadPool(2); 6 7 public static void main(String[] args) { 8 9 threadPool.execute(new Runnable() { 10 @Override 11 public void run() { 12 try { 13 String A = "银行流水A";// A录入银行流水数据 14 exgr.exchange(A); 15 } catch (InterruptedException e) { 16 } 17 } 18 }); 19 20 threadPool.execute(new Runnable() { 21 @Override 22 public void run() { 23 try { 24 String B = "银行流水B";// B录入银行流水数据 25 String A = exgr.exchange("B"); 26 System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:" 27 + A + ",B录入是:" + B); 28 } catch (InterruptedException e) { 29 } 30 } 31 }); 32 33 threadPool.shutdown(); 34 35 } 36 }
3、这个类在遇到类似生产者和消费者问题时,是非常有用的。来一个非常经典的并发问题:你有相同的数据buffer,一个或多个数据生产者,和一个或多个数据消费者。只是Exchange类只能同步2个线程,所以你只能在你的生产者和消费者问题中只有一个生产者和一个消费者时使用这个类。
在这个指南,你将学习如何使用 Exchanger 类来解决只有一个生产者和一个消费者的生产者和消费者问题。
按照这些步骤来实现下面的例子:
1 package tool; 2 import java.util.List; 3 import java.util.concurrent.Exchanger; 4 5 //1. 首先,从实现producer开始吧。创建一个类名为Producer并一定实现 Runnable 接口。 6 public class Producer implements Runnable { 7 8 // 2. 声明 List<String>对象,名为 buffer。这是等等要被相互交换的数据类型。 9 private List<String> buffer; 10 11 // 3. 声明 Exchanger<List<String>>; 对象,名为exchanger。这个 exchanger 对象是用来同步producer和consumer的。 12 private final Exchanger<List<String>> exchanger; 13 14 // 4. 实现类的构造函数,初始化这2个属性。 15 public Producer(List<String> buffer, Exchanger<List<String>> exchanger) { 16 this.buffer = buffer; 17 this.exchanger = exchanger; 18 } 19 20 // 5. 实现 run() 方法. 在方法内,实现10次交换。 21 @Override 22 public void run() { 23 int cycle = 1; 24 for (int i = 0; i < 10; i++) { System.out.printf("Producer: Cycle %d\n", cycle); 25 26 // 6. 在每次循环中,加10个字符串到buffer。 27 for (int j = 0; j <10; j++) { 28 String message = "Event " + ((i * 10) + j); 29 System.out.printf("Producer: %s\n", message); 30 buffer.add(message); 31 } 32 33 // 7. 调用 exchange() 方法来与consumer交换数据。此方法可能会抛出InterruptedException 异常, 加上处理代码。 34 try { 35 buffer = exchanger.exchange(buffer); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 System.out.println("Producer: " + buffer.size()); 40 cycle++; 41 } 42 } 43 }
1 //8. 现在, 来实现consumer。创建一个类名为Consumer并一定实现 Runnable 接口。 2 package tool; 3 import java.util.List; 4 import java.util.concurrent.Exchanger; 5 public class Consumer implements Runnable { 6 7 // 9. 声明名为buffer的 List<String>对象。这个对象类型是用来相互交换的。 8 private List<String> buffer; 9 10 // 10. 声明一个名为exchanger的 Exchanger<List<String>> 对象。用来同步 producer和consumer。 11 private final Exchanger<List<String>> exchanger; 12 13 // 11. 实现类的构造函数,并初始化2个属性。 14 public Consumer(List<String>buffer, Exchanger<List<String>> exchanger) { 15 this.buffer = buffer; 16 this.exchanger = exchanger; 17 } 18 19 // 12. 实现 run() 方法。在方法内,实现10次交换。 20 @Override 21 public void run() { 22 int cycle = 1; 23 for (int i = 0; i < 10; i++) { 24 System.out.printf("Consumer: Cycle %d\n", cycle); 25 26 // 13. 在每次循环,首先调用exchange()方法来与producer同步。Consumer需要消耗数据。此方法可能会抛出InterruptedException异常, 加上处理代码。 27 try { 28 buffer = exchanger.exchange(buffer); 29 } catch (InterruptedException e) { e.printStackTrace(); 30 } 31 32 // 14. 把producer发来的在buffer里的10字符串写到操控台并从buffer内删除,留空。System.out.println("Consumer: " + buffer.size()); 33 for (int j = 0; j <10; j++) { 34 String message = buffer.get(0); 35 System.out.println("Consumer: " + message); 36 buffer.remove(0); 37 } 38 cycle++; 39 }
1 //15.现在,实现例子的主类通过创建一个类,名为Core并加入 main() 方法。 2 package tool; 3 import java.util.ArrayList; 4 mport java.util.List; 5 import java.util.concurrent.Exchanger; 6 7 public class Core { 8 public static void main(String[] args) { 9 10 // 16. 创建2个buffers。分别给producer和consumer使用. 11 List<String> buffer1 = new ArrayList<String>(); 12 List<String> buffer2 = new ArrayList<String>(); 13 14 // 17. 创建Exchanger对象,用来同步producer和consumer。 15 Exchanger<List<String>> exchanger = new Exchanger<List<String>>(); 16 17 // 18. 创建Producer对象和Consumer对象。 18 Producer producer = new Producer(buffer1, exchanger); 19 Consumer consumer = new Consumer(buffer2, exchanger); 20 21 // 19. 创建线程来执行producer和consumer并开始线程。 22 Thread threadProducer = new Thread(producer); 23 Thread threadConsumer = new Thread(consumer); threadProducer.start(); 24 threadConsumer.start(); 25 }
消费者开始时是空白的buffer,然后调用Exchanger来与生产者同步。因为它需要数据来消耗。生产者也是从空白的buffer开始,然后创建10个字符串,保存到buffer,并使用exchanger与消费者同步。
在这儿,2个线程(生产者和消费者线程)都是在Exchanger里并交换了数据类型,所以当消费者从exchange() 方法返回时,它有10个字符串在buffer内。当生产者从 exchange() 方法返回时,它有空白的buffer来重新写入。这样的操作会重复10遍。
如你执行例子,你会发现生产者和消费者是如何并发的执行任务和在每个步骤它们是如何交换buffers的。与其他同步工具一样会发生这种情况,第一个调用 exchange()方法会进入休眠直到其他线程的达到。