JAVA多线程系列--并发工具类(CountDownLatch, CyclicBarrier, Semaphore,Exchanger)

前言

  本节笔者将详细讲下CountDownLatch, CyclicBarrier, Semaphore,Exchanger 这四个并发工具类的使用。
  这4个工具类在高并发的场景下,也是使用广泛。

1.1 CountDownLatch简介

  CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

1.2 CountDownLatch使用场景

    1.实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
    2.开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
    3.死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

1.3CountDownLatch 例子

/**
 * CountDownLatch
 * @author niyuelin
 *
 */
public class CountDownLatchDemo {
    final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);// 两个工人的协作
        Worker worker1 = new Worker("zhang san", 5000, latch);
        Worker worker2 = new Worker("li si", 1000, latch);
        worker1.start();//
        worker2.start();//
        latch.await();// 等待所有工人完成工作
        System.out.println("all work done at " + sdf.format(new Date()));
    }

    static class Worker extends Thread {
        String workerName;
        int workTime;
        CountDownLatch latch;

        public Worker(String workerName, int workTime, CountDownLatch latch) {
            this.workerName = workerName;
            this.workTime = workTime;
            this.latch = latch;
        }

        public void run() {
            System.out.println("Worker " + workerName + " do work begin at " + sdf.format(new Date()));
            doWork();// 工作了
            System.out.println("Worker " + workerName + " do work complete at " + sdf.format(new Date()));
            latch.countDown();// 工人完成工作,计数器减一

        }

        private void doWork() {
            try {
                Thread.sleep(workTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.4 输出结果

Worker li si do work begin at 2017-12-14 18:52:46
Worker zhang san do work begin at 2017-12-14 18:52:46
Worker li si do work complete at 2017-12-14 18:52:47
Worker zhang san do work complete at 2017-12-14 18:52:51
all work done at 2017-12-14 18:52:51

2.1 CyclicBarrier简介

  CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
  CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

2.2 CyclicBarrier使用场景

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

2.3 CyclicBarrier例子

/**
 * CyclicBarrier
 * @author niyuelin
 *
 */
public class CyclicBarrierDemo2 {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier = new CyclicBarrier(N, new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程" + Thread.currentThread().getName());
            }
        });

        for (int i = 0; i < N; i++)
            new Writer(barrier).start();
    }

    static class Writer extends Thread {
        private CyclicBarrier cyclicBarrier;

        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程" + Thread.currentThread().getName() + "正在写入数据...");
            try {
                Thread.sleep(5000); // 以睡眠来模拟写入数据操作
                System.out.println("线程" + Thread.currentThread().getName() + "写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}

2.4 输出结果

线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
当前线程Thread-1
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...

3.1 Semaphore简介

  Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
  Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

3.2 Semaphore使用场景

    1.流量控制,特别公用资源有限的应用场景,比如数据库连接
    2.对某组资源的访问权限

3.3 Semaphore例子

/**
 * Semaphore
 * @author niyuelin
 *
 */
public class SemaphoreTest {

    private static final int THREAD_COUNT = 30;
    private static ExecutorService executorService = Executors.newFixedThreadPool(30);

    private static Semaphore s = new Semaphore(10);
    private static int in = 0;

    public static synchronized void add(){
        in++;
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i =0; i<THREAD_COUNT; i++){
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        s.acquire();
                        Thread.sleep(1000);
                        add();
//                      System.out.println("save data");
                        s.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }
        Thread.sleep(1500);
        System.out.println(in);
        Thread.sleep(1000);
        System.out.println(in);
        executorService.shutdown();
    }
}

3.4 输出结果

10
29

4.1 Exchanger简介

  Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

4.2 Exchanger使用场景

    1.遗传算法,遗传算法里需要选出两个人作为交配对象,这时候会交换两人的数据,并使用交叉规则得出2个交配结果。
    2.校对工作 我们需要将纸制银流通过人工的方式录入成电子银行流水,为了避免错误,需要两个线程相互校对。

4.3 Exchanger例子

/**
 * Exchanger
 * @author niyuelin
 *
 */
public class ExChangerTest {
    private static final Exchanger<String> exgr = new Exchanger<String>();
    private static ExecutorService executorService = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String A = "银行流水A";
                    String c = exgr.exchange(A);
                    System.out.println("c: "+ c);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        executorService.execute(new Runnable() {
            public void run() {
                try {
                    String B = "银行流水B";
                    String A = exgr.exchange(B);
                    System.out.println("A:"+A+" ,B:"+B);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        executorService.shutdown();
    }
}

4.4 输出结果

c:银行流水B
A:银行流水A ,B:银行流水B

猜你喜欢

转载自blog.csdn.net/niyuelin1990/article/details/78804854