Exchanger(交换器)是Java并发包中的一个工具类,用于两个线程之间交换数据。它提供了一个同步点,当两个线程都到达该点时,它们可以交换数据,并且在交换完成后继续执行。
Exchanger 的主要用途是在两个线程之间安全地交换数据。实现一种互相等待的机制,直到两个线程都到达同步点后才继续执行。它可以用于解决一些特定的并发问题,例如生产者-消费者问题中的缓冲区交换数据、两个线程之间的数据同步等。
Exchanger 提供了一个 exchange() 方法,用于交换数据。当一个线程调用 exchange() 方法时,如果另一个线程也调用了 exchange() 方法,那么两个线程将会交换数据。如果只有一个线程调用了 exchange() 方法,那么它将会被阻塞,直到另一个线程也调用了 exchange() 方法。
使用 Exchanger 的方法如下:
import java.util.concurrent.Exchanger;
public class ExchangerExample {
private Exchanger<String> exchanger = new Exchanger<>();
// 线程1
public void thread1() {
try {
// 执行线程1的业务逻辑
String data1 = "线程1的业务数据";
String exchangedData = exchanger.exchange(data1);
// exchangedData 是线程2的业务数据,在这里可以继续处理交换后的数据
// 执行线程1的业务逻辑
} catch (InterruptedException e) {
}
}
// 线程2
public void thread2() {
try {
// 执行线程2的业务逻辑
String data2 = "线程2的业务数据";
String exchangedData = exchanger.exchange(data2);
// exchangedData 是线程1的业务数据,在这里可以继续处理交换后的数据
// 执行线程2的业务逻辑
} catch (InterruptedException e) {
}
}
}
在下面示例中,创建两个线程,然后在两个线程中先模拟执行一段业务逻辑,再进行交换数据,交换后再模拟执行一段业务逻辑的过程。
- 当一个线程调用 exchange() 方法时,它将等待另一个线程也调用 exchange() 方法。当两个线程都到达 exchange() 方法时,它们将交换数据并继续执行。
- 从执行中可以看出交换前的业务逻辑一定是在交换数据之前执行的,其实这也是一个同步点。在这点上跟前面讲的屏障CyclicBarrier和分阶段任务Phaser有点像。
package top.yiqifu.study.p004_thread;
import java.util.concurrent.Exchanger;
public class Test091_Exchanger {
public static void main(String[] args) {
ExchangerExample exchangerExample = new ExchangerExample();
new Thread(()->{ exchangerExample.thread1(); }).start();
new Thread(()->{ exchangerExample.thread2(); }).start();
}
public static class ExchangerExample {
private Exchanger<String> exchanger = new Exchanger<>();
// 线程1
public void thread1() {
try {
System.out.println("线程1交换前,执行业务逻辑");
this.sleep();
String data1 = "线程1的业务数据";
String exchangedData = exchanger.exchange(data1);
System.out.println("线程1拿到交换后的数据是:"+exchangedData);
this.sleep();
System.out.println("线程1交换后,执行业务逻辑");
} catch (InterruptedException e) {
}
}
// 线程2
public void thread2() {
try {
System.out.println("线程2交换前,执行业务逻辑");
this.sleep();
String data2 = "线程2的业务数据";
String exchangedData = exchanger.exchange(data2);
System.out.println("线程2拿到交换后的数据是:"+exchangedData);
this.sleep();
System.out.println("线程2交换后,执行业务逻辑");
} catch (InterruptedException e) {
}
}
// 模拟业务耗时
private void sleep(){
try {
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在下面示例中,实现一个碎文件(如代码文件)拷贝程序,这里我把 D:\Java\ 目录拷贝到 D:\Java-bak\ 进行一个数据备份。
在这个示例中,我使用一个线程 scanThread 进行文件名扫描,然后用 10个线程 copyThread 进行文件拷贝。在扫描线程将扫描到的文件使用 List curCopiedFiles = exchanger.exchange(exchangeList); 方法将数据交换给拷贝线程,同时使用接收已经拷贝的文件名信息。在拷贝线程中同样使用 exchanger.exchange 方法接收扫描文件信息,并把拷贝文件信息交换给扫描线程进行统计。
package top.yiqifu.study.p004_thread;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Exchanger;
public class Test091_ExchangerFileCopy {
//private static final String SOURCE_DIR = "D:\\";
//private static final String DEST_DIR = "E:\\";
private static final String SOURCE_DIR = "D:\\Java\\";
private static final String DEST_DIR = "D:\\Java-bak\\";
private static final int EXCHANGE_FILE_COUNT = 100;
private static volatile boolean isFinish = false;
private static final Exchanger<List<String>> exchanger = new Exchanger<>();
private static final List<String> scannedFiles = new ArrayList<>();
private static final List<String> copiedFiles = new ArrayList<>();
private static final List<Thread> copyThreads = new ArrayList<>();
public static void main(String[] args) {
Thread scanThread = new Thread(Test091_ExchangerFileCopy::scanFiles);
scanThread.start();
for (int i = 0; i < 10; i++) {
Thread copyThread = new Thread(Test091_ExchangerFileCopy::copyFiles);
copyThreads.add(copyThread);
copyThread.start();
}
try {
scanThread.join();
for (Thread copyThread : copyThreads) {
copyThread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("扫描文件数: " + scannedFiles.size());
System.out.println("拷贝文件数: " + copiedFiles.size());
}
private static void scanFiles() {
List<String> curScannedFiles = new ArrayList<>();
scanDirectory(new File(SOURCE_DIR), curScannedFiles);
isFinish = true;
try {
if(curScannedFiles.size() > 0){
List<String> exchangeList = new ArrayList<>(curScannedFiles);
curScannedFiles.clear();
List<String> curCopiedFiles = exchanger.exchange(exchangeList);
showCopyFiles(exchangeList, curCopiedFiles);
}
while (isCopyThreadAlive()) {
if(isCopyThreadInState(Thread.State.WAITING)) {
// 表示拷贝线程调用了 exchanger.exchange,然后处于等待中。这里交换一个空值过去,让他退出。
exchanger.exchange(null);
}
Thread.sleep(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void scanDirectory(File directory, List<String> curScannedFiles) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile()) {
curScannedFiles.add(file.getAbsolutePath());
if (curScannedFiles.size() >= EXCHANGE_FILE_COUNT) {
try {
List<String> exchangeList = new ArrayList<>(curScannedFiles);
curScannedFiles.clear();
List<String> curCopiedFiles = exchanger.exchange(exchangeList);
showCopyFiles(exchangeList, curCopiedFiles);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else if (file.isDirectory()) {
scanDirectory(file, curScannedFiles);
}
}
}
}
private static boolean isCopyThreadAlive(){
for(Thread t : copyThreads){
if(t.getState() != Thread.State.TERMINATED){
return true;
}
}
return false;
}
private static boolean isCopyThreadInState(Thread.State state){
for(Thread t : copyThreads){
if(t.getState() == state){
return true;
}
}
return false;
}
private static void showCopyFiles(List<String> curScannedFiles, List<String> curCopiedFiles) {
scannedFiles.addAll(curScannedFiles);
copiedFiles.addAll(curCopiedFiles);
System.out.println(String.format("扫描总文件数:%d,拷贝文件数:%d,当前拷贝文件数:%d", scannedFiles.size(), copiedFiles.size(), curCopiedFiles.size()));
}
private static void copyFiles() {
List<String> curCopiedFiles = new ArrayList<>();
while (true) {
List<String> scannedFiles = getCopyFiles(curCopiedFiles);
curCopiedFiles.clear();
if (scannedFiles == null || scannedFiles.size() == 0) {
break;
}
for (String filePath : scannedFiles) {
File sourceFile = new File(filePath);
File destFile = new File(DEST_DIR + filePath.replace(SOURCE_DIR, ""));
if(destFile.exists()){
continue;
}
destFile.mkdirs();
try {
Files.copy(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
curCopiedFiles.add(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static synchronized List<String> getCopyFiles(List<String> copiedFiles) {
if(isFinish){
return null;
}
try {
List<String> exchangeList = new ArrayList<>(copiedFiles);
return exchanger.exchange(exchangeList);
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
}