Java 多线程之 Exchanger (数据交换/同步辅助类)

文章目录

    • 一、概述
    • 二、使用方法
    • 三、测试示例1
    • 四、测试示例2

一、概述

  • 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;
            }
        }
    
    
    }
    
    

你可能感兴趣的:(#,Java,多线程,java,开发语言,多线程)