请选择 进入手机版 | 继续访问电脑版
零一零零 门户 IT技术 Java并发 查看内容

拓展并发编程思维,一个使用CountDownLatch的程序Demo

2020-5-25 14:59| 发布者: x0100| 查看: 335| 评论: 0

摘要: 拓展并发编程思维,一个使用CountDownLatch的程序Demo

         CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。CountDown方法和Await方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用CountDown方法时,计数器会被减1;当调用Await方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被CountDown方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用Await方法都会直接返回。

使用CountDownLatch模拟如下场景:

张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。

  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. import java.util.Random;
  4. import java.util.concurrent.CountDownLatch;
  5. public class Customer implements Runnable {
  6. private CountDownLatch latch;
  7. private String name;
  8. public Customer(CountDownLatch latch, String name) {
  9. this.latch = latch;
  10. this.name = name;
  11. }
  12. @Override
  13. public void run() {
  14. try {
  15. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
  16. Random random = new Random();
  17. System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
  18. Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
  19. System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
  20. latch.countDown();
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. import java.util.concurrent.CountDownLatch;
  4. public class Waitress implements Runnable {
  5. private CountDownLatch latch;
  6. private String name;
  7. public Waitress(CountDownLatch latch, String name) {
  8. this.latch = latch;
  9. this.name = name;
  10. }
  11. @Override
  12. public void run() {
  13. try {
  14. SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
  15. System.out.println(sdf.format(new Date()) + " " + name + "等待顾客");
  16. latch.await();
  17. System.out.println(sdf.format(new Date()) + " " + name + "开始上菜");
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import java.util.concurrent.CountDownLatch;
  4. public class CountDownLatchTester {
  5. public static void main(String[] args) throws InterruptedException {
  6. CountDownLatch latch = new CountDownLatch(3);
  7. List<Thread> threads = new ArrayList<>(3);
  8. threads.add(new Thread(new Customer(latch, "张三")));
  9. threads.add(new Thread(new Customer(latch, "李四")));
  10. threads.add(new Thread(new Customer(latch, "王五")));
  11. for (Thread thread : threads) {
  12. thread.start();
  13. }
  14. Thread.sleep(100);
  15. new Thread(new Waitress(latch, "♥小芳♥")).start();
  16. }
  17. }

运行结果

  1. 15:25:53.015 王五出发去饭店
  2. 15:25:53.015 李四出发去饭店
  3. 15:25:53.015 张三出发去饭店
  4. 15:25:53.062 ♥小芳♥等待顾客
  5. 15:25:54.341 张三到了饭店
  6. 15:25:54.358 李四到了饭店
  7. 15:25:56.784 王五到了饭店
  8. 15:25:56.784 ♥小芳♥开始上菜

Await方法的另一个重载,传入等待的超时时间,比如服务员只等3秒钟,可以把服务员类中的:

latch.await();

改成:

latch.await(3, TimeUnit.SECONDS);

CountDownLatch的实现原理

CountDownLatch有一个内部类叫做Sync,它继承了AbstractQueuedSynchronizer类,其中维护了一个整数state,并且保证了修改state的可见性和原子性。

创建CountDownLatch实例时,也会创建一个Sync的实例,同时把计数器的值传给Sync实例,具体是这样的: 

 

  1. public CountDownLatch(int count) {
  2. if (count < 0) throw new IllegalArgumentException("count < 0");
  3. this.sync = new Sync(count);
  4. }

CountDown方法中,只调用了Sync实例的ReleaseShared方法,具体是这样的:

  1. public void countDown() {
  2. sync.releaseShared(1);
  3. }

其中的ReleaseShared方法,先对计数器进行减1操作,如果减1后的计数器为0,唤醒被Await方法阻塞的所有线程,具体是这样的:

  1. public final boolean releaseShared(int arg) {
  2. if (tryReleaseShared(arg)) { //对计数器进行减一操作
  3. doReleaseShared();//如果计数器为0,唤醒被await方法阻塞的所有线程
  4. return true;
  5. }
  6. return false;
  7. }

其中的TryReleaseShared方法,先获取当前计数器的值,如果计数器为0时,就直接返回;如果不为0时,使用CAS方法对计数器进行减1操作,具体是这样的:

  1. protected boolean tryReleaseShared(int releases) {
  2. for (;;) {//死循环,如果CAS操作失败就会不断继续尝试。
  3. int c = getState();//获取当前计数器的值。
  4. if (c == 0)// 计数器为0时,就直接返回。
  5. return false;
  6. int nextc = c-1;
  7. if (compareAndSetState(c, nextc))// 使用CAS方法对计数器进行减1操作
  8. return nextc == 0;//如果操作成功,返回计数器是否为0
  9. }
  10. }

在Await方法中,只调用了Sync实例的AcquireSharedInterruptibly方法,具体是这样的:

  1. public void await() throws InterruptedException {
  2. sync.acquireSharedInterruptibly(1);
  3. }

其中AcquireSharedInterruptibly方法,判断计数器是否为0,如果不为0则阻塞当前线程,具体是这样的:

  1. public final void acquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. if (Thread.interrupted())
  4. throw new InterruptedException();
  5. if (tryAcquireShared(arg) < 0)//判断计数器是否为0
  6. doAcquireSharedInterruptibly(arg);//如果不为0则阻塞当前线程
  7. }

其中TryAcquireShared方法,是AbstractQueuedSynchronizer中的一个模板方法,其具体实现在Sync类中,其主要是判断计数器是否为零,如果为零则返回1,如果不为零则返回-1,具体是这样的:

  1. protected int tryAcquireShared(int acquires) {
  2. return (getState() == 0) ? 1 : -1;
  3. }

 


鲜花

握手

雷人

路过

鸡蛋

最新评论


QQ|Archiver|手机版|小黑屋| 零一零零 ( 京ICP备20003964号 ) |网站地图

GMT+8, 2020-10-24 20:36 , Processed in 0.037617 second(s), 17 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

返回顶部