Java 中并发工具类 CountDownLatch、CyclicBarrier、Semaphore和Exchanger

JDK 的并发包中提供了几个非常有用的并发控制工具类。CountDownLatch\CyclicBarrier 和Semaphore 提供了一种并发流程控制的手段,Exchanger 工具类提供了线程间交换数据的一种手段。

1.CountDownLatch

CountDownLatch 允许一个或多个线程等待其他线程完成操作。
CountDownLatch 是通过“共享锁” 实现的。 在创建 CountDownLatch 时,会传递一个 int 类型参数, 该参数是“锁计数器” 的初始状态, 表示该“共享锁”最 多 能 被 count 个 线 程 同 时 获 取 , 这 个 值 只 能 被 设 置 一 次 , 而 且CountDownLatch 没有提供任何机制去重新设置这个计数值。 主线程必须在启动其他线程后立即调用 await()方法。 这样主线程的操作就会在这个方法上阻塞, 直到其他线程完成各自的任务。 当某线程调用该 CountDownLatch 对象的await()方法时, 该线程会等待“共享锁” 可用时, 才能获取“共享锁” 进而继续运行。 而“共享锁” 可用的条件, 就是“锁计数器” 的值为 0! 而“锁计数器”的初始值为 count, 每当一个线程调用该 CountDownLatch 对象的 countDown()方法时, 才将“锁计数器” -1; 通过这种方式, 必须有 count 个线程调用countDown()之后, “锁计数器” 才为 0, 而前面提到的等待线程才能继续运行!

场景:解析一个Excel里的多个sheet数据,使用多个线程,每个线程解析一个sheet,等到所有线程解析完,主线程完成解析
解决方案一:
在主线程中调用启动每一个线程,然后调用个线程的.join()方法。
解析:join()用于让当前线程等待join线程执行结束,就是不停检查join线程是否存活,如果存活则让当前线程永远等待。直到join线程执行结束后 this.notifyAll().
解决方案二:

public class Test {
     public static void main(String[] args) {    
         final CountDownLatch latch = new CountDownLatch(2);          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");

                    Thread.sleep(3000);
                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();          
         new Thread(){
            public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");

                     Thread.sleep(3000);
                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
         try {
             System.out.println("等待2个子线程执行完毕...");
            latch.await();
            System.out.println("2个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     } 
}

解析:CountDownLatch 的构造函数接受一个int类型的参数作为计数器,如果想等待N个线程完成,这里传入N。
public void countDown() { }; //将count值减1
public void await() throws InterruptedException { }; //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行

2.同步屏障 CyclicBarrier

让一组线程达到一个同步点时被阻塞,知道最后一个线程达到同步点时候,屏障才会开们,所有被拦截的线程才会继续执行。

await()函数每被调用一次,计数便会减少 1(CyclicBarrier 设置了初始值),并阻塞住当前线程。 当计数减至 0 时, 阻塞解除, 所有在此 CyclicBarrier 上面阻塞的线程开始运行

场景:假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情。

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;inew 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("所有线程写入完毕,继续处理其他任务...");
        }

    }

}

解析:当调用await()方法之后,线程就处于barrier了,知道所有线程达到同步点以后可以继续处理别的事情。

3.CountDownLatch 和 CyclicBarrier 的区别

(1) CountDownLatch 的作用是允许 1 个线程等待其他线程执行完成之后,它才执行; 而 CyclicBarrier 则是允许 N 个线程相互等待到某个公共屏障点, 然后这一组线程再同时执行。

(2) CountDownLatch 的计数器的值无法被重置, 这个初始值只能被设置一次, 是不能够重用的; CyclicBarrier 是可以重用的。

4.Semaphore

可以控制某个资源可被同时访问的个数, 通过构造函数设定一定数量的许可, 通过 acquire() 获取一个许可, 如果没有就等待, 而 release() 释放一个许可

场景:要读取几万个文件的数据,可以启动几十个线程并发读取,同时需要连接数据库完成存储,而数据库连接数只有是个,这时候我们只有控制十个线程获取数据库的链接保存数据,否则报错无法获取链接。

public class SemaphoreTest{
    private static final int THREAD_COUNT = 30;
    private static ExecutorServicethreadPool = Executors.new FixedThreadPool(THREAD_COUNT);
    private static Semaphore s =new Semaphore(10);
    public static void main(String[] args){
        for(int i=0;inew Runnable(){
            public void run(){
            try{
            s.acquire();
            ...
            s.release();
            }catch (InterruptedException e){}
            }
            }
        });
    threadPool.shutdown();

解析:虽然有三十个线程在执行,但是只允许10个并发执行。acquire()获取一个许可证,release()方法归还一个许可证。

你可能感兴趣的:(Java,并发编程)