一个长头发、穿着清爽的小姐姐,拿着一个崭新的Mac笔记本向我走来,看着来势汹汹,我心想着确定是技术大佬吧!可是我也是一个才华横溢的人,稳住咱们能赢。
java
面试官:看你简历上有写熟悉并发编程,CountDownLatch必定用过吧,跟我说说它!面试
我:CountDownLatch是JDK提供的一个同步工具,它可让一个或多个线程等待,一直等到其余线程中执行完成一组操做。编程
面试官:CountDownLatch有哪些经常使用的方法?微信
我:有countDown
方法和await
方法,CountDownLatch在初始化时,须要指定用给定一个整数做为计数器。当调用countDown
方法时,计数器会被减1;当调用await
方法时,若是计数器大于0时,线程会被阻塞,一直到计数器被countDown
方法减到0时,线程才会继续执行。计数器是没法重置的,当计数器被减到0时,调用await
方法都会直接返回。并发
面试官:调用
countDown
方法时,线程也会阻塞嘛?dom
我:不会的,调用countDown
的线程能够继续执行,不须要等待计数器被减到0,只是调用await方法的线程须要等待。ide
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。工具
面试官:能够举一个使用CountDownLatch的例子吗?测试
我:好比张3、李四和王五几我的约好去饭店一块儿去吃饭,这几我的都是比较绅士,要等到全部人都到齐之后才让服务员上菜。这种场景就能够用到CountDownLatch。ui
面试官:能够写一下吗?
我:固然能够,这是张3、李四和王五的顾客类:
package onemore.study; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.CountDownLatch; public class Customer implements Runnable { private CountDownLatch latch; private String name; public Customer(CountDownLatch latch, String name) { this.latch = latch; this.name = name; } @Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); Random random = new Random(); System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店"); Thread.sleep((long) (random.nextDouble() * 3000) + 1000); System.out.println(sdf.format(new Date()) + " " + name + "到了饭店"); latch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }
这是服务员类:
package onemore.study; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CountDownLatch; public class Waitress implements Runnable { private CountDownLatch latch; private String name; public Waitress(CountDownLatch latch, String name) { this.latch = latch; this.name = name; } @Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); System.out.println(sdf.format(new Date()) + " " + name + "等待顾客"); latch.await(); System.out.println(sdf.format(new Date()) + " " + name + "开始上菜"); } catch (Exception e) { e.printStackTrace(); } } }
而后,再写一个测试类,用于模拟上面所说的场景:
package onemore.study; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; public class CountDownLatchTester { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); List<Thread> threads = new ArrayList<>(3); threads.add(new Thread(new Customer(latch, "张三"))); threads.add(new Thread(new Customer(latch, "李四"))); threads.add(new Thread(new Customer(latch, "王五"))); for (Thread thread : threads) { thread.start(); } Thread.sleep(100); new Thread(new Waitress(latch, "♥小芳♥")).start(); } }
运行之后的结果应该是这样的:
15:25:53.015 王五出发去饭店 15:25:53.015 李四出发去饭店 15:25:53.015 张三出发去饭店 15:25:53.062 ♥小芳♥等待顾客 15:25:54.341 张三到了饭店 15:25:54.358 李四到了饭店 15:25:56.784 王五到了饭店 15:25:56.784 ♥小芳♥开始上菜
能够看到,服务员小芳在调用await
方法时一直阻塞着,一直等到三个顾客都调用了countDown
方法才继续执行。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
面试官:若是有一个顾客迟迟没到,饭店都打样了,也不能一直等啊,应该这么办?
我:可使用await
方法的另外一个重载,传入等待的超时时间,好比服务员只等3秒钟,能够把服务员类中的
latch.await();
改为:
latch.await(3, TimeUnit.SECONDS);
运行结果多是这样的:
17:24:40.915 张三出发去饭店 17:24:40.915 李四出发去饭店 17:24:40.915 王五出发去饭店 17:24:40.948 ♥小芳♥等待顾客 17:24:43.376 李四到了饭店 17:24:43.544 王五到了饭店 17:24:43.951 ♥小芳♥开始上菜 17:24:44.762 张三到了饭店
能够看到,服务员小芳在调用await
方法时虽然被阻塞了,可是时间超过3秒后,没等顾客张三调用countDown
方法就继续执行开始上菜了。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
面试官:CountDownLatch的实现原理是什么?
我:CountDownLatch有一个内部类叫作Sync,它继承了AbstractQueuedSynchronizer类,其中维护了一个整数state
,而且保证了修改state
的可见性和原子性。
建立CountDownLatch实例时,也会建立一个Sync的实例,同时把计数器的值传给Sync实例,具体是这样的:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
在 countDown
方法中,只调用了Sync实例的releaseShared
方法,具体是这样的:
public void countDown() { sync.releaseShared(1); }
其中的releaseShared
方法,先对计数器进行减1操做,若是减1后的计数器为0,唤醒被await方法阻塞的全部线程,具体是这样的:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //对计数器进行减一操做 doReleaseShared();//若是计数器为0,唤醒被await方法阻塞的全部线程 return true; } return false; }
其中的tryReleaseShared
方法,先获取当前计数器的值,若是计数器为0时,就直接返回;若是不为0时,使用CAS方法对计数器进行减1操做,具体是这样的:
protected boolean tryReleaseShared(int releases) { for (;;) {//死循环,若是CAS操做失败就会不断继续尝试。 int c = getState();//获取当前计数器的值。 if (c == 0)// 计数器为0时,就直接返回。 return false; int nextc = c-1; if (compareAndSetState(c, nextc))// 使用CAS方法对计数器进行减1操做 return nextc == 0;//若是操做成功,返回计数器是否为0 } }
在await
方法中,只调用了Sync实例的acquireSharedInterruptibly
方法,具体是这样的:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
其中acquireSharedInterruptibly
方法,判断计数器是否为0,若是不为0则阻塞当前线程,具体是这样的:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0)//判断计数器是否为0 doAcquireSharedInterruptibly(arg);//若是不为0则阻塞当前线程 }
其中tryAcquireShared
方法,是AbstractQueuedSynchronizer中的一个模板方法,其具体实如今Sync类中,其主要是判断计数器是否为零,若是为零则返回1,若是不为零则返回-1,具体是这样的:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
面试官:嗯,很不错,立刻给你发offer。
微信公众号:万猫学社
微信扫描二维码
得到更多Java技术干货