线程协作,控制并发流程(CountDownLatch,Semaphore,Condition,CyclicBarrier)

线程协作,控制并发流程

控制并发流程的工具类,作用就是帮助我们更容易得让线程之间合作,让线程之间相互配合,来满足业务逻辑

常见并发控制工具类

线程协作,控制并发流程(CountDownLatch,Semaphore,Condition,CyclicBarrier)_第1张图片

CountDownLatch 计数器

countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了

主要方法介绍
CountDownLatch(int count)

只提供了一个构造方法,参数count为需要倒数的数值

await()

调用await()方法的线程将会被挂起,它会等待直到count的值为0才继续执行

countDown()

没调用一次数值减1,直到为0时,等待的线程会被唤醒

代码演示
  • 一个线程等待多个线程都执行完毕,在继续自己的工作
public class countDownLatchDemo {


    public static void main(String[] args) throws InterruptedException {
        CountDownLatch downLatch = new CountDownLatch(5);

        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 5; i++) {
            int finalI = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep((long)Math.random()*100000);
                        System.out.println("NO  "+ finalI + "检查完成");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        downLatch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待5个人检查完成");
        downLatch.await();
        System.out.println("检查完成");
    }
}
  • 多个线程等待某一个线程的信号,同时开始执行

模拟跑步的场景,5个人等待裁判员的发令枪。只要一声令下,所有人同时开始跑步

public class countDownLatchDemo2 {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch downLatch = new CountDownLatch(1);

        ExecutorService service = Executors.newFixedThreadPool(15);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {

                        System.out.println("NO  "+ finalI + "开始等待发令");
                        downLatch.await();
                        System.out.println("NO  "+ finalI + "开始跑步");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);

        }
        Thread.sleep(5000);
        System.out.println("发令枪响起,开始跑步");
        downLatch.countDown();
    }
}

模拟跑步的场景,5个人等待裁判员的发令枪。只要一声令下,所有人同时开始跑步. 终点的裁判员等待最后一名运动员到达终点后,宣布运动会结束

public class countDownLatchDemo2 {


    public static void main(String[] args) throws InterruptedException {
        CountDownLatch downLatch = new CountDownLatch(1);
        CountDownLatch downLatch2 = new CountDownLatch(15);
        ExecutorService service = Executors.newFixedThreadPool(15);

        for (int i = 0; i < 15; i++) {
            int finalI = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {

                        System.out.println("NO  "+ finalI + "开始等待发令");
                        downLatch.await();
                        System.out.println("NO  "+ finalI + "开始跑步");
                        Thread.sleep((long)Math.random()*10000);
                        downLatch2.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);

        }

        Thread.sleep(5000);
        System.out.println("发令枪响起,开始跑步");

        downLatch.countDown();

        System.out.println("裁判员等待");
        downLatch2.await();

        System.out.println("最后一名运行员到达,比赛结束");
    }

}

Semaphore 信号量

用来限制和管理优先资源的使用情况

信号量的作用是维护一个"许可证"的计数,线程获取许可证,那许可证的剩余就减一,线程也可以释放一个许可证,那么许可证数量就加一,当信号量拥有的许可证的数量为0时,那么下一个获取许可证的线程将会等待,直到另外的线程释放了许可证

信号量使用流程
  • 初始化Semaphore 并指定许可证数量

  • 在需要被限制的代码前acquire() 或者acquireUninterruptibly()方法

  • 任务结束后,调用release()释放许可证

信号量的重要方法
new Semaphore(int permits,boolean fair)

这里可以设置是否要使用公平策略,如果传入true,那么semaphore会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证可以分发给之前等了最长时间的线程

acquire()/acquireUninterruptibly()
acquire(int permits)

一次拿到指定数量的许可证

tryAcquire()

看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话没关系,不比陷入阻塞,可以去做别的是事,过会再来查看许可证的空闲情况

tryAcquire(timeout)

和tryAcquire()一样,但是多了一个超时时间,比如“在3秒内获取不到许可证,我就去做别的事”

release()

释放许可证

release(int permits)

释放指定数量的许可证

代码演示
public class SemaphoreDemo {
   static  Semaphore se = new Semaphore(3,true);
   public static void main(String[] args) {


       ExecutorService service = Executors.newFixedThreadPool(50);

       for (int i = 0; i < 100; i++) {

           service.submit(new Task());
       }

       service.shutdown();
   }

   static class Task implements Runnable{


       @Override
       public void run() {
           try {
               se.acquire();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+" 拿到许可证");

           try {
               Thread.sleep(2000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+" 释放许可证");
           se.release();
       }
   }
}

注意点
  • 获取和释放的许可证必须是一致,否则会导致许可证数量不够用,会导致程序卡死

  • 并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也是A获取了,然后由B释放,只要逻辑合理即可。

Condition

当线程1需要等待某个条件的时候,它就去执行condition.await()方法,一旦执行了await()方法,线程就会进入阻塞状态

然后通常会有另外一个线程,去执行对应的条件,直到这个条件达成的时候,调用condition.signal()方法,这是JVM就会从被阻塞的线程栈里面找到被阻塞的线程,当线程1就会收到可执行信号的时候,线程的状态就会变成Runnable可执行状态

主要方法
signalAll()

唤起所有的正在等待的线程

signal()

只唤起等待时间最长的线程

代码演示
基本用法
public class ConditionDemo {

    private ReentrantLock reentrantLock = new ReentrantLock();

    private Condition condition = reentrantLock.newCondition();

    void method1() throws InterruptedException {
        reentrantLock.lock();
        try{
            System.out.println("条件不满足");
            condition.await();
            System.out.println("条件满足,开始执行");
        }finally {
            reentrantLock.unlock();
        }
    }

    void method2() throws InterruptedException {
        reentrantLock.lock();
        try{
            System.out.println("准备工作完成,开始唤醒其他线程");
            condition.signal();
        }finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionDemo conditionDemo = new ConditionDemo();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    conditionDemo.method1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                try {
                    conditionDemo.method2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        new Thread(runnable).start();
        new Thread(runnable1).start();

    }

}

生产者消费者
public class ConditionDemo2 {
 private static Integer queueSize = 10;
 private static PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
 private ReentrantLock reentrantLock = new ReentrantLock();

 private Condition notEmpty = reentrantLock.newCondition();
 private Condition notFull = reentrantLock.newCondition();

 public static void main(String[] args) {
     ConditionDemo2 cd = new ConditionDemo2();
     producer producer = cd.new producer();
     consumer cs = cd.new consumer();
     producer.start();
     cs.start();
 }

 class producer extends Thread {

     @Override
     public void run() {
         while(true){

             try {
                 reentrantLock.lock();
                 if(queue.size() == queueSize){
                     System.out.println("队列已满");
                     try {
                         notFull.await();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 queue.offer(1);
                 notEmpty.signal();
             }finally {
                 reentrantLock.unlock();
             }

         }

     }
 }


 class consumer extends Thread {

     @Override
     public void run() {

         while(true){

             try{
                 reentrantLock.lock();
                 if(queue.size() == 0){
                     System.out.println("队列为空");
                     try {
                         notEmpty.await();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(queue.poll());
                 notFull.signal();
             }finally {
                 reentrantLock.unlock();
             }
         }
     }
 }
}

CyclicBarrier

CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程

当有大量线程相互配合,分别计算不同任务,并且需要最后统一汇总的时候,我们可以使用CyclicBarrier。CyclicBarrier可以构造一个集结点,当某一个线程执行完毕,他就会到集结点等待,知道所有线程都到了集结点,那么栅栏就被撤销,所有线程在统一出发,继续执行剩下的任务。

代码演示
public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("人全部都到了");
            }
        });

        for (int i = 0; i < 5; i++) {
            new Thread(new Task(i,cb)).start();
        }
    }

    static class Task implements Runnable{
         private int id;
         private CyclicBarrier cyclicBarrier;

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程 "+id+" "+"前往集合地点");
            try {
                Thread.sleep(5000);
                System.out.println("线程 "+id+" "+"到达集合地点,等到其他人");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

/**

线程 0 前往集合地点
线程 1 前往集合地点
线程 2 前往集合地点
线程 3 前往集合地点
线程 4 前往集合地点
线程 2 到达集合地点,等到其他人
线程 0 到达集合地点,等到其他人
线程 3 到达集合地点,等到其他人
线程 1 到达集合地点,等到其他人
线程 4 到达集合地点,等到其他人
人全部都到了


*/
CyclicBarrier与CountDownLatch的区别
  • 作用不同

    CyclicBarrier要等到固定数量的线程都达到了栅栏位置才能继续执行,而CountDownLatch只需要等待数字到0 。CountDownLatch用于事件,CyclicBarrier用于线程

  • 可重用性

    CountDownnLatch在倒数到0并触发门闩打开后,就不能再次使用了,除非新建新的实例,而CyclicBarrier可以重复使用

你可能感兴趣的:(并发工具类总结)