一、概述
-
Exchanger(交换器)是Java并发包中的一个工具类,用于两个线程之间交换数据。它提供了一个同步点,当两个线程都到达该点时,它们可以交换数据,并且在交换完成后继续执行。
-
Exchanger 的主要用途是在两个线程之间安全地交换数据。实现一种互相等待的机制,直到两个线程都到达同步点后才继续执行。它可以用于解决一些特定的并发问题,例如生产者-消费者问题中的缓冲区交换数据、两个线程之间的数据同步等。
-
Exchanger 提供了一个 exchange() 方法,用于交换数据。当一个线程调用 exchange() 方法时,如果另一个线程也调用了 exchange() 方法,那么两个线程将会交换数据。如果只有一个线程调用了 exchange() 方法,那么它将会被阻塞,直到另一个线程也调用了 exchange() 方法。
- exchange(V x) 方法:将当前线程的数据 x 与对方线程的数据进行交换,并返回对方线程的数据。
- exchange(V x, long timeout, TimeUnit unit) 方法:带有超时参数的交换数据方法,如果在指定的时间内没有另一个线程到达交换点,则返回 null。
二、使用方法
-
使用 Exchanger 的方法如下:
- 声明一个 Exchanger 对象。
- 在两个线程中调用 exchanger.exchange 进行交换数据。
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) { } } }
三、测试示例1
-
在下面示例中,创建两个线程,然后在两个线程中先模拟执行一段业务逻辑,再进行交换数据,交换后再模拟执行一段业务逻辑的过程。
- 当一个线程调用 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(); } } } }
四、测试示例2
-
在下面示例中,实现一个碎文件(如代码文件)拷贝程序,这里我把 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; } } }