Java中的并发工具类

前言:
JDK1.5中增加了几个并发工具类,CountDownLatch,CyclicBarrier,Semaphore分别提供了一种并发流程控制的手段,Exchanger则提供了在线程间交换数据的一种手段.

1.等待多线程完成的CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作.例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

package com.h.concurrent;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch :闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行
 * 示例:利用闭锁计算各个线程执行完成所需的时间
 * 让主线程等待计算偶数的5个任务线程全部执行完后再统计总的执行时间
 */
public class TestCountDownLatch {
    public static void main(String[] args) {
        /**
         * 任务:打印[0,50000]之间的所有偶数,并计算耗费时间
         * 实现:开启5个线程,并发执行任务,在main主线程中汇总耗费的时间
         */
        /**
         * 1.使用join()
         * join用于让当前执行线程等待join线程执行结束,其实现原理是不停检查join线程是否存活,
         * 如果是则让当前线程永远等待
         * 代码核心:
         * while(isAlive){
         *     wait(0);
         * }
         * 直到join线程终止后,线程的this.notifyAll()会被调用.
         */
        long start = System.currentTimeMillis();
        Runnable task = () -> {
            for (int i = 0; i <= 50000; i++) {
                if (i % 2 == 0) {
                    System.out.println(i);
                }
            }
        };
        Thread thread = null;
        for (int i = 0; i < 5; i++) {
            thread = new Thread(task);
            thread.start();
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start) + "ms");
	//================================================================
        /**
         * 2.使用CountDownLatch
         */
        long start = System.currentTimeMillis();
        final CountDownLatch latch = new CountDownLatch(5);
        //
        LatchDemo lt = new LatchDemo(latch);
        for (int i = 0; i < 5; i++) {
            new Thread(lt).start();
        }

        try {
        //主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。
        //这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start) + "ms");
    }
}

class LatchDemo implements Runnable {
    private CountDownLatch latch;
    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i <= 50000; i++) {
                if (i % 2 == 0) {
                    System.out.println(i);
                }
            }
        } finally {
            latch.countDown();
        }
    }
}

CountDownLatch latch = new CountDownLatch(int N);
await()会阻塞当前线程,直到传入的计数器N变为0.由于countDown()方法可以用在任何地方,因此这里说的计数器N,可以是N个线程,也可以是1个线程里的N个步骤.用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可,此时,计数器的初始值就为线程的数量,每当一个线程完成了自己的任务后,计数器的值就会减1,当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。如果某个线程处理的比较慢,不想让主线程一直等待,可以使用await(long time,TimeUnit unit),这个方法在等待特定时间后,就会不再阻塞当前线程.join也有类似的方法.另:CountDownLatch的计数器值一旦初始化就不能再改变,一个线程调用countDown()方法happens-before另外一个线程调用await()方法.
在这里插入图片描述

应用场景:

  • 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
  • 开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。

2.同步屏障CyclicBarrier
CyclicBarrier:可循环使用的屏障,它让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行.
构造方法
(1)CyclicBarrier cb = new CyclicBarrier(int parties),parties表示屏障拦截的线程数量,每个线程调用await()方法告诉CyclicBarrier我已到达了同步点,然后当前线程被阻塞.
(2)CyclicBarrier cb = new CyclicBarrier(int parties,Runnable barrierAction)用于在所有线程到达屏障时,优先执行barrierAction,方便处理复杂的场景啊.
在这里插入图片描述

package com.h.concurrent;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by John on 2018/5/12.
 */
public class TestCyclicBarrier {
    public static void main(String[] args) {
        final CyclicBarrier barrier = new CyclicBarrier(2, () -> System.out.println("Thread:" + Thread.currentThread().getName()+ " " +3));
	 
        new Thread(() -> {
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("Thread:" + Thread.currentThread().getName()+ " " + 1);
        },"Child-Thread").start();

        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }

        System.out.println("Thread:" + Thread.currentThread().getName()+ " " + 2);
    }
}
打印结果:
Thread:Child-Thread 3
Thread:Child-Thread 1
Thread:main 2
package com.h.concurrent;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by John on 2018/5/12.
 */
public class TestCyclicBarrier {

   private static final CyclicBarrier barrier = new CyclicBarrier(2, () -> System.out.println("Thread:" + Thread.currentThread().getName()+ " " +3));

    public static void main(String[] args) {
        new Thread(new BarrierTask(barrier,1),"Child-Thread").start();
        new Thread(new BarrierTask(barrier,2),"Child-Thread").start();
    }
}

class BarrierTask implements Runnable{
    private CyclicBarrier barrier;
    private int value;

    public BarrierTask(CyclicBarrier barrier,int value) {
        this.barrier = barrier;
        this.value = value;
    }

    @Override
    public void run() {
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("Thread:" + Thread.currentThread().getName()+ " " + value);
    }
}
打印结果有2种情况:
Thread:Child-Thread 3
Thread:Child-Thread 1
Thread:Child-Thread 2:
Thread:Child-Thread 3
Thread:Child-Thread 2
Thread:Child-Thread 1
不论哪种结果,3一定是第一个被打印出来的

应用场景
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景.

package com.h.concurrent;

import java.util.OptionalDouble;
import java.util.concurrent.*;

/**
 * Created by John on 2018/5/12.
 * 需求:在[1,500],[501,1000],[1001,1500],[1501,2000]的四个区间内分别产生50个随机整数,计算出产生的所有随机整数的平均值
 * 开启四个字线程并行产生每个区间内的随机整数,并计算总和,最后在统计整体平均值
 */
public class TestCyclicBarrier {

    public static void main(String[] args) {
        BarrierTask barrierTask = new BarrierTask();
        barrierTask.count();
    }
}

class BarrierTask implements Runnable {
    /**
     * 创建4个屏障,处理完之后执行当前类的run方法
     */
    private CyclicBarrier barrier = new CyclicBarrier(4, this);

    private ExecutorService executor = Executors.newFixedThreadPool(4);

    /**
     * 保存每个线程计算的随机数的和
     */
    private ConcurrentHashMap<String, Integer> sunCountMap = new ConcurrentHashMap<>(4);

    public void count() {
        for (int i = 0; i < 4; i++) {
            final int index = i + 1;
            executor.execute(() -> {
                sunCountMap.put(Thread.currentThread().getName(), getRandomSum((index - 1) * 500 + 1, index * 500));
                try {
                    System.out.println("当前等待执行计算的线程数量:" + barrier.getNumberWaiting());
                    //计算完成,插入一个屏障
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }

    @Override
    public void run() {
        OptionalDouble average = sunCountMap.values().stream().mapToDouble(n -> n).average();
        System.out.println(average.getAsDouble());
    }

    private int getRandomSum(int start, int end) {
        int sum = 0;
        for (int i = 0; i < 50; i++) {
            sum += ThreadLocalRandom.current().nextInt(start, end);
        }
        return sum;
    }
}
打印结果:
当前等待执行计算的线程数量:0
当前等待执行计算的线程数量:0
当前等待执行计算的线程数量:1
当前等待执行计算的线程数量:1
50740.75

CyclicBarrier与CountDownLatch的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,所以CyclicBarrier能处理更为复杂的场景.例如:如果计算发生错误,可以重置计数器,让线程重新执行一次.CyclicBarrier还提供其他有用的方法,如getNUmberWaiting方法可以获得CyclicBarrier阻塞的线程数量;isBroken()方法用来了解阻塞的线程是否被中断.

3.Java中如何模拟真正的同时并发请求?
有时需要测试一下某个功能的并发性能,又不想借助于其他工具,索性就自己的开发语言,来一个并发请求就最方便了.Java 中模拟并发请求,自然是很方便的,只要多开几个线程,发起请求就好了。但是,这种请求,一般会存在启动的先后顺序了,算不得真正的同时并发!怎么样才能做到真正的同时并发呢?闭锁 CountDownLatch与栅栏CyclicBarrier, 刚好就用来做这种事就最合适了。

package com.h.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by John on 2018/10/18.
 * 模拟并发请求
 * 1.开启n个线程,加一个闭锁,开启所有线程;
 * 2.待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。
 */
public class ConcurrencyRequestTest1 {
    public static long startTaskAllInOnce(int threadNums,final Runnable task){
        //开启门
        final CountDownLatch startGate = new CountDownLatch(1);
        //关闭门
        final CountDownLatch endGate = new CountDownLatch(threadNums);
        for (int i=0;i<threadNums;i++){
            Thread t = new Thread(() -> {
                try {
                    //设置屏障,是线程在此等待,当开启门打开是,一起涌入门中
                    startGate.await();
                    try {
                        //让一同涌入门中的所有线程同时执行任务
                        task.run();
                    } finally {
                        //每执行完一个线程,就将结束门-1,当减到0时,关闭结束门.
                        endGate.countDown();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
        long startTime = System.nanoTime();
        System.out.println(startTime + " [" + Thread.currentThread() + "] All thread is ready, concurrent going...");
        // 因开启门只需一个开关,所以立马就开启开始门
        startGate.countDown();
        // 等结束门关闭
        try {
            endGate.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.nanoTime();
        System.out.println(endTime + " [" + Thread.currentThread() + "] All thread is completed.");
        return endTime - startTime;
    }

    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger();
        Runnable task = () -> {
            for (int i=0;i<5;i++){
                //模拟请求处理耗时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int iCounter = count.incrementAndGet();
                System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter);
            }
        };

        startTaskAllInOnce(5,task);
    }
}

在这里插入图片描述

在这里插入图片描述

此处设置了一道门,以保证所有线程可以同时生效。但是,此处的同时启动,也只是语言层面的东西,也并非绝对的同时并发。具体的调用还要依赖于CPU个数,线程数及操作系统的线程调度功能等,不过咱们也无需纠结于这些了,重点在于理解原理!
与 CountDownLatch 有类似功能的,还有个工具栅栏 CyclicBarrier, 也是提供一个等待所有线程到达某一点后,再一起开始某个动作,效果一致,不过栅栏的目的确实比较纯粹,就是等待所有线程到达,而前面说的闭锁 CountDownLatch 虽然实现的也是所有线程到达后再开始,但是他的触发点其实是最后那一个开关,所以侧重点是不一样的。
简单看一下栅栏是如何实现真正同时并发呢?示例如下:

package com.h.concurrent;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by John on 2018/10/18.
 * 模拟并发请求
 * 1.开启n个线程,加一个闭锁,开启所有线程;
 * 2.待所有线程都准备好后,按下开启按钮,就可以真正的发起并发请求了。
 */
public class ConcurrencyRequestTest2 {

    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger();
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                //模拟请求处理耗时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int iCounter = count.incrementAndGet();
                System.out.println(System.nanoTime() + " [" + Thread.currentThread().getName() + "] iCounter = " + iCounter);
            }
        };

        startTaskAllInOnce(5, task);
    }

    private static void startTaskAllInOnce(int threadNums, Runnable finishTask) {
        // 设置栅栏解除时的动作,比如初始化某些值
        CyclicBarrier barrier = new CyclicBarrier(threadNums, finishTask);
        // 启动 n 个线程,与栅栏阀值一致,即当线程准备数达到要求时,栅栏刚好开启,从而达到统一控制效果
        for (int i = 0; i < threadNums; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(new CounterTask(barrier)).start();
        }
        System.out.println(Thread.currentThread().getName() + " out over...");
    }
}

class CounterTask implements Runnable {
    private CyclicBarrier barrier;

    public CounterTask(final CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " is ready...");
        try {
            //设置屏障,使在此等待
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " - " + System.currentTimeMillis() + " started...");
    }
}

在这里插入图片描述
各有其应用场景吧,关键在于需求。就本文示例的需求来说,个人更愿意用闭锁一点,因为更可控了,但是代码却是多了.

4.控制并发线程数的Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源.
关键API
Semaphore(int permits) permits表示许可证数量,或者叫最大并发数
int availablePermits():返回信号量中当前可用的许可证数量
int getQueueLength():返回正在等待获取许可证的线程数
应用场景
Semaphore可以用来做流量控制,特别是公用资源有限的场景,比如数据库连接.

package com.h.concurrent;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Created by John on 2018/5/12.
 */
public class TestSemaphore {
    public static void main(String[] args) {
        final int THREAD_COUNT = 10;
        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
        Semaphore semaphore = new Semaphore(5);
        for (int i=0;i<THREAD_COUNT;i++){
            threadPool.execute(new SemaphoreDemo(String.valueOf(i),semaphore));
        }
        threadPool.shutdown();
    }
}

class SemaphoreDemo implements Runnable{
    private String id;
    private Semaphore semaphore;
    private static Random random = new Random(100);

    public SemaphoreDemo(String id, Semaphore semaphore) {
        this.id = id;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println(this.id + "is using the toilet");
            TimeUnit.MICROSECONDS.sleep(random.nextInt(2000));
            System.out.println(this.id + "is leaving");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印如下:
D:\OpenSources\JDK\32bit\jdk1.8.0_77\bin\java -Didea.launcher.port=7533 -Didea.launcher.bin.path=D:\IntelliJ_IDEA_15.0.2\bin -Dfile.encoding=UTF-8 -classpath D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\charsets.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\deploy.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\access-bridge.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\cldrdata.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\dnsns.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\jaccess.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\jfxrt.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\localedata.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\nashorn.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\sunec.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\sunjce_provider.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\sunmscapi.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\sunpkcs11.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\ext\zipfs.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\javaws.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\jce.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\jfr.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\jfxswt.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\jsse.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\management-agent.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\plugin.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\resources.jar;D:\OpenSources\JDK\32bit\jdk1.8.0_77\jre\lib\rt.jar;E:\IDEAProjects\test\target\classes;D:\Maven\maven_repo\junit\junit\4.12\junit-4.12.jar;D:\Maven\maven_repo\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\Maven\maven_repo\com\google\guava\guava\26.0-jre\guava-26.0-jre.jar;D:\Maven\maven_repo\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;D:\Maven\maven_repo\org\checkerframework\checker-qual\2.5.2\checker-qual-2.5.2.jar;D:\Maven\maven_repo\com\google\errorprone\error_prone_annotations\2.1.3\error_prone_annotations-2.1.3.jar;D:\Maven\maven_repo\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;D:\Maven\maven_repo\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\Maven\maven_repo\com\alibaba\fastjson\1.2.51\fastjson-1.2.51.jar;D:\Maven\maven_repo\org\apache\commons\commons-lang3\3.8.1\commons-lang3-3.8.1.jar;D:\Maven\maven_repo\com\fasterxml\jackson\core\jackson-databind\2.9.7\jackson-databind-2.9.7.jar;D:\Maven\maven_repo\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\Maven\maven_repo\com\fasterxml\jackson\core\jackson-core\2.9.7\jackson-core-2.9.7.jar;D:\Maven\maven_repo\org\apache\poi\poi\4.0.0\poi-4.0.0.jar;D:\Maven\maven_repo\commons-codec\commons-codec\1.10\commons-codec-1.10.jar;D:\Maven\maven_repo\org\apache\commons\commons-collections4\4.2\commons-collections4-4.2.jar;D:\Maven\maven_repo\org\apache\poi\poi-ooxml\4.0.0\poi-ooxml-4.0.0.jar;D:\Maven\maven_repo\org\apache\poi\poi-ooxml-schemas\4.0.0\poi-ooxml-schemas-4.0.0.jar;D:\Maven\maven_repo\org\apache\xmlbeans\xmlbeans\3.0.1\xmlbeans-3.0.1.jar;D:\Maven\maven_repo\org\apache\commons\commons-compress\1.18\commons-compress-1.18.jar;D:\Maven\maven_repo\com\github\virtuald\curvesapi\1.04\curvesapi-1.04.jar;D:\IntelliJ_IDEA_15.0.2\lib\idea_rt.jar com.intellij.rt.execution.application.AppMain com.h.concurrent.TestSemaphore
1is using the toilet
0is using the toilet
2is using the toilet
0is leaving
4is using the toilet
3is using the toilet
5is using the toilet
3is leaving
1is leaving
6is using the toilet
5is leaving
4is leaving
7is using the toilet
6is leaving
2is leaving
8is using the toilet
9is using the toilet
7is leaving
9is leaving
8is leaving

Process finished with exit code 0

5.线程间交换数据的Exchanger
Exchanger是一个用于线程间协作的工具类,它提供一个同步点,在这个同步点,两个线程可以通过exchange()方法交换彼此的数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据.
应用场景
Exchanger可以用于遗传算法或校对工作.

package com.h.concurrent;

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by John on 2018/10/18.
 * 银行流水录入校对
 */
public class ExchangerTest {
    private static final Exchanger<String> exgr = new Exchanger<>();

    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threadPool.execute(() -> {
            String A = "银行流水";//A录入银行流水数据
            try {
                exgr.exchange(A);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadPool.execute(() -> {
            String B = "银行流水";//B录入银行流水数据
            try {
                String A = exgr.exchange(B);
                System.out.println("A和B数据是否一致:" + A.equals(B) + ",A录入的是:" + A + ",B录入的是:" + B);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadPool.shutdown();
    }
}
打印结果:
A和B数据是否一致:true,A录入的是:银行流水,B录入的是:银行流水

如果两个线程有一个没有执行exchange()方法,则会一直等待,如果不想因为特殊情况造成一直等待,可以用exchange(Object data,long tiemout,TimeUnit unit)设置最大等待时长.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值