并发编程专题七:CountDownLatch&Semaphore应用与原理

一、Semaphore是什么

  Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目,底层依赖AQS的状态State,是在生产当中比较常用的一个工具类。

二、怎么使用Semaphore

  构造方法

 1 public Semaphore(int permits)

2 public Semaphore(int permits, boolean fair) 

    * permits表示许可线程的数量

    * fair表示公平性,如果这个设为true的话,下次执行的线程会是等待最久的线程

  重要方法

1 public void acquire() throws InterruptedException
2 public void release()
3 tryAcquire(int args,long timeout, TimeUnit unit)

    * acquire()表示阻塞并获取许可

    * release()表示释放许可

  基本使用

    需求场景

      资源访问,服务限流(Hystrix里限流就有基于信号量方式)。

    代码实现

 1 public class SemaphoreRunner {
 2     public static void main(String[] args) {
 3         Semaphore semaphore = new Semaphore(2);
 4         for (int i=0;i<5;i++){
 5             new Thread(new Task(semaphore,"yangguo+"+i)).start();
 6         }
 7     }
 8 
 9     static class Task extends Thread{
10         Semaphore semaphore;
11 
12         public Task(Semaphore semaphore,String tname){
13             this.semaphore = semaphore;
14             this.setName(tname);
15         }
16 
17         public void run() {
18             try {
19                 semaphore.acquire();               
20                 System.out.println(Thread.currentThread().getName()+":aquire() at time:"+System.currentTimeMillis());
21                 Thread.sleep(1000);
22                 semaphore.release();               
23                 System.out.println(Thread.currentThread().getName()+":aquire() at time:"+System.currentTimeMillis());
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27 
28         }
29     }
30 }

打印结果
Thread-3:aquire() at time:1563096128901
Thread-1:aquire() at time:1563096128901
Thread-1:aquire() at time:1563096129903
Thread-7:aquire() at time:1563096129903
Thread-5:aquire() at time:1563096129903
Thread-3:aquire() at time:1563096129903
Thread-7:aquire() at time:1563096130903
Thread-5:aquire() at time:1563096130903
Thread-9:aquire() at time:1563096130903
Thread-9:aquire() at time:1563096131903

  从打印结果可以看出,一次只有两个线程执行 acquire(),只有线程进行 release() 方法后才会有别的线程执行 acquire()。

  CountDownLatch使用及应用场景例子

    CountDownLatch是什么

      CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

      使用场景:Zookeeper分布式锁,Jmeter模拟高并发等

    CountDownLatch如何工作

      CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

      API

CountDownLatch.countDown()
CountDownLatch.await();

     CountDownLatch应用场景例子

        比如陪媳妇去看病。

        医院里边排队的人很多,如果一个人的话,要先看大夫,看完大夫再去排队交钱取药。
        现在我们是双核,可以同时做这两个事(多线程)。
        假设看大夫花3秒钟,排队交费取药花5秒钟。我们同时搞的话,5秒钟我们就能完成,然后一起回家(回到主线程)。
      代码清单:
 1 /**
 2  * 看大夫任务
 3  */
 4 public class SeeDoctorTask implements Runnable {
 5     private CountDownLatch countDownLatch;
 6 
 7     public SeeDoctorTask(CountDownLatch countDownLatch){
 8         this.countDownLatch = countDownLatch;
 9     }
10 
11     public void run() {
12         try {
13             System.out.println("开始看医生");
14             Thread.sleep(3000);
15             System.out.println("看医生结束,准备离开病房");
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }finally {
19             if (countDownLatch != null)
20                 countDownLatch.countDown();
21         }
22     }
23 
24 }
25 
26 /**
27  * 排队的任务
28  */
29 public class QueueTask implements Runnable {
30 
31     private CountDownLatch countDownLatch;
32 
33     public QueueTask(CountDownLatch countDownLatch){
34         this.countDownLatch = countDownLatch;
35     }
36     public void run() {
37         try {
38             System.out.println("开始在医院药房排队买药....");
39             Thread.sleep(5000);
40             System.out.println("排队成功,可以开始缴费买药");
41         } catch (InterruptedException e) {
42             e.printStackTrace();
43         }finally {
44             if (countDownLatch != null)
45                 countDownLatch.countDown();
46         }
47     }
48 }
49 
50 /**
51  * 配媳妇去看病,轮到媳妇看大夫时
52  * 我就开始去排队准备交钱了。
53  */
54 public class CountDownLaunchRunner {
55 
56     public static void main(String[] args) throws InterruptedException {
57         long now = System.currentTimeMillis();
58         CountDownLatch countDownLatch = new CountDownLatch(2);
59 
60         new Thread(new SeeDoctorTask(countDownLatch)).start();
61         new Thread(new QueueTask(countDownLatch)).start();
62         //等待线程池中的2个任务执行完毕,否则一直
63         countDownLatch.await();
64         System.out.println("over,回家 cost:"+(System.currentTimeMillis()-now));
65     }
66 }

  CyclicBarrier

  栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

    API

cyclicBarrier.await();

  应用场景

  可以用于多线程计算数据,最后合并计算结果的场景。例如,用一个Excel保存了用户所有银行流水,每个Sheet保存一个账户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
  代码清单:
 1 public class CyclicBarrierRunner implements Runnable {
 2     private CyclicBarrier cyclicBarrier;
 3     private int index ;
 4 
 5     public CyclicBarrierTest(CyclicBarrier cyclicBarrier, int index) {
 6         this.cyclicBarrier = cyclicBarrier;
 7         this.index = index;
 8     }
 9 
10     public void run() {
11         try {
12             System.out.println("index: " + index);
13             index--;
14             cyclicBarrier.await();
15         } catch (Exception e) {
16             e.printStackTrace();
17         }
18     }
19 
20     public static void main(String[] args) throws Exception {
21         CyclicBarrier cyclicBarrier = new CyclicBarrier(11, new Runnable()                               {
22             public void run() {
23                 System.out.println("所有特工到达屏障,准备开始执行秘密任务");
24             }
25         });
26         for (int i = 0; i < 10; i++) {
27             new Thread(new CyclicBarrierTest(cyclicBarrier, i)).start();
28         }
29         cyclicBarrier.await();
30         System.out.println("全部到达屏障....");
31     }
32 }

  Executors

    主要用来创建线程池,代理了线程池的创建,使得你的创建入口参数变得简单

    重要方法:    

      * newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

      * newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

      * newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

      * newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  Exchanger

    当一个线程运行到exchange()方法时会阻塞,另一个线程运行到exchange()时,二者交换数据,然后执行后面的程序。
    应用场景
      极少,大家了解即可
代码清单:
 1 public class ExchangerTest {
 2 
 3     public static void main(String []args) {
 4         final Exchanger<Integer> exchanger = new Exchanger<Integer>();
 5         for(int i = 0 ; i < 10 ; i++) {
 6             final Integer num = i;
 7             new Thread() {
 8                 public void run() {
 9                     System.out.println("我是线程:Thread_" + this.getName() + "我的数据是:" + num);
10                     try {
11                         Integer exchangeNum = exchanger.exchange(num);
12                         Thread.sleep(1000);
13                         System.out.println("我是线程:Thread_" + this.getName() + "我原先的数据为:" + num + " , 交换后的数据为:" + exchangeNum);
14                     } catch (InterruptedException e) {
15                         e.printStackTrace();
16                     }
17                 }
18             }.start();
19         }
20     }
21 }
原文地址:https://www.cnblogs.com/Mapi/p/14406449.html