JAVA高并发学习(三)——JDK并发包

一、CountDownLatch:倒计时器(发令枪)

CountDownLatch的主要功能是让所有线程都结束操作后,才继续执行后面的操作。

在声明一个CountDownLatch的时候,需要给定一个count(int类型),我们可以通过CountDownLatch提供的countDown()方法,对这个值进行减1操作。当这个值为0的时候,执行后续操作。

这个工具类有点类似于ES6中的Promise.all( ... ) 方法,但是又更灵活。

首先,我们每个线程可以多次执行countDown()方法,同时也可以选择在线程执行的中间某个地方执行countDown()。这样对业务流程的控制就要更简单和轻松。只要最后count值减到0即可。

而有些地方写的count必须对应线程数,也就是一个错误的说法。因为一个线程可以多次countDown()。

public class CountDownLatchDemo {
    private static CountDownLatch latch = new CountDownLatch(6);
    //单个countDown执行线程
    private static class CDRunnable implements Runnable{
        public void run() {
            SleepTools.ms(new Random().nextInt(10)*1000);
            System.out.println("线程"+Thread.currentThread().getId()
                    +":开始执行 ");
            SleepTools.ms(new Random().nextInt(10)*1000);
            latch.countDown();
            // countDown()之后可以继续进行操作
            System.out.println("线程"+Thread.currentThread().getId()
                    +":完成操作,进入等待 ");
        }
    }

    //其他线程等待latch完成
    private static class OtherThread implements Runnable{

        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("指令完成,非主线程开始执行操作");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //一个线程中执行多次countDown()。
        new Thread(new Runnable() {
            public void run() {
                System.out.println("分步线程:执行第一步");
                latch.countDown();
                SleepTools.ms(new Random().nextInt(10)*1500);
                System.out.println("分步线程:执行第二步");
                latch.countDown();
            }
        }).start();
        new Thread(new OtherThread()).start();
        for(int i=0;i < 4;i++){
            Thread thread = new Thread(new CDRunnable());
            thread.start();
        }
        latch.await();
        System.out.println("主线程继续进行其他操作");
    }
    //休眠工具类
    private static class SleepTools {
        public static void ms(int i) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {}
        }
    }
}

二、CyclicBarrier:循环栅栏

CyclicBarrier的功能和CountDownLatch非常接近,但是不同的是,一个CyclicBarrier是可以循环使用的。而循环条件就是凑齐parties个线程(不完全正确)。CyclicBarrier的构造方法需要传入两个参数

public CyclicBarrier(int parties, Runnable barrierAction);

parties表示要接收多少个线程进行一次循环(准确说是接收了多少个cyclic.await()指令,一个线程可以多次调用这个方法,就和CountDownLatch一样)。barrierAction表示每次循环执行的操作。

接下来是《Java高并发程序设计》中的一个司令官和士兵的例子,士兵集合完成,进行一次统计。完成任务后,再进行一次统计。这里已经把sleep方法给封装成工具类,不再详说,可以参考CountDownLatch中的SleepTools。

public class CyclicBarrierDemo {
    //士兵
    static class Soldier implements Runnable{
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(String soldier, CyclicBarrier cyclic) {
            this.soldier = soldier;
            this.cyclic = cyclic;
        }

        @Override
        public void run() {
            try {
                //第一次执行计数,也就是第一次线程的统计。
                cyclic.await();
                SleepTools.random(15);
                System.out.println(soldier+":任务完成!");
                //第二次执行计数,也就是第二次对线程的统计。
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
    //司令官
    static class BarrierRun implements Runnable{
        boolean flag = false;
        int N;
        public BarrierRun(int n) {
            N = n;
        }
        @Override
        public void run() {
            if (flag){
                System.out.println("司令:【士兵"+N+"个,任务完成!】");
            }else {
                System.out.println("司令:【士兵"+N+"个,集合完毕!】");
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        int N = 10;
        CyclicBarrier cyclic = new CyclicBarrier(N,new BarrierRun(N));
        for (int i=0; i < N; i++){
            System.out.println("士兵" + i + "报道");
            new Thread(new Soldier("士兵"+i,cyclic)).start();
        }
    }
}

三、LockSupport:线程阻塞工具

LockSupport的存在主要是通过park()和unpark()方法替换了Thread.suspend()和Thread.resume()。像在前一章的例子中,如果把suspend()和resume()全部换成park()和unpark()方法,程序就能正常的运行。

而当使用park()把线程挂起的时候,也不会出现让人费解的Runnable状态,而是WAITING状态,更便于问题的排查。使用方法和suspend那些一样,这里不再多说了。

四、Semaphore:信号量

在一般情况下,当一个资源被锁住后,只会允许一个线程来访问它。

而使用Semaphore则可以让多个线程同时访问这一个资源。

初始化一个Semaphore的时候,会指定一个permits来决定这个锁有多少个许可。通过acquire()、tryAcquire()和acquireUninterruptibly()方法可以去获取一个许可。分别是“获取/等待或中断”、“获取/返回false”、“获取/等待但是不响应中断”。

 

你可能感兴趣的:(Semaphore,CountDownLatch,并发,JAVA)