当前位置: 首页 > 后端技术 > Java

CountDownLatch

时间:2023-04-02 00:21:40 Java

,一位留着长发、穿着整齐的长发女士,拿着一台全新的Mac笔记本电脑来找我。看着来势汹汹的样子,我心想,她一定是个技术老大。酒吧!不过我也是个人才,坚持下去就能赢。面试官:看你简历上对并发编程很熟悉,你肯定用过CountDownLatch,说说吧!我:CountDownLatch是JDK提供的一个同步工具,可以让一个或多个线程等待,直到一组操作在其他线程完成。面试官:CountDownLatch常用的方法有哪些?我:有countDown方法和await方法。CountDownLatch在初始化时,需要指定一个给定的整数作为计数器。当countDown方法被调用时,计数器会减1;当调用await方法时,如果计数器大于0,则线程将被阻塞,直到通过countDown方法将计数器减为0,线程才会继续执行。计数器无法重置。当计数器减为0时,调用await方法会直接返回。面试官:调用countDown方法时线程会不会被阻塞?我:不行,调用countDown的线程可以继续执行,不用等待计数器归0,但是调用await方法的线程需要等待。面试官:能举个使用CountDownLatch的例子吗?我:比如张三、李四、王舞约好一起去饭店吃饭。这些人比较绅士,等人都到齐了,服务员才会上菜。这种场景可以使用CountDownLatch。面试官:你能写下来吗?我:当然,这是张三、李四、王五的客户类:packageonemore.study;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.Random;importjava.util.并发.CountDownLatch;publicclassCustomerimplementsRunnable{privateCountDownLatchlatch;私有字符串名称;publicCustomer(CountDownLatchlatch,Stringname){this.latch=latch;this.name=名称;}@Overridepublicvoidrun(){try{SimpleDateFormatsdf=newSimpleDateFormat("HH:mm:ss.SSS");随机random=newRandom();System.out.println(sdf.format(newDate())+""+name+"goHotel");Thread.sleep((long)(random.nextDouble()*3000)+1000);System.out.println(sdf.format(newDate())+""+name+"到达酒店");latch.countDown();}catch(Exceptione){e.printStackTrace();}}}这是服务器类:packageonemore.study;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.CountDownLatch;publicclassWaitressimplementsRunnable{privateCountDownLatchlatch;私有字符串名称;publicWaitress(CountDownLatchlatch,Stringname){this.latch=latch;this.name=名称;}@Overridepublicvoidrun(){try{SimpleDateFormatsdf=newSimpleDateFormat("HH:mm:ss.SSS");System.out.println(sdf.format(newDate())+""+name+"等待客户");闩锁。等待();System.out.println(sdf.format(newDate())+""+name+"开始服务");}catch(Exceptione){e.printStackTrace();}}}然后,编写一个测试类来模拟上面提到的场景:packageonemore.study;importjava.util.ArrayList;importjava.util。列表;导入java.util.concurrent.CountDownLatch;公共类CountDownLatchTester{publicstaticvoidmain(String[]args)抛出InterruptedException{CountDownLatchlatch=newCountDownLatch(3);列表<线程>threads=newArrayList<>(3);threads.add(newThread(newCustomer(latch,"张三")));threads.add(newThread(newCustomer(latch,"李四")));threads.add(newThread(newCustomer(latch,"王舞")));for(Threadthread:threads){thread.start();}Thread.sleep(100);newThread(newWaitress(latch,"?小芳?")).start();for(Threadthread:threads){thread.join();}}}运行后结果应该是这样的: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方法再继续面试官:如果有个顾客很久没来,餐厅已经校对了,你总不能一直等,怎么办?我:你可以使用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?小芳?waiting顾客17:24:43.376李四到店17:24:43.544王五到店17:24:43.951?小芳?开始上菜17:24:44.762张三到店可以看到服务员小芳在调用await方法时虽然被阻塞了,但是过了3多秒后,继续执行,在顾客张三调用countDown方法之前就把食物送上来了。面试官:CountDownLatch的实现原理是什么?我:CountDownLatch有一个内部类Sync,它继承了AbstractQueuedSynchronizer类,维护一个整型状态,保证修改状态的可见性和原子性。在创建CountDownLatch实例的同时,也创建了一个Sync实例,同时将计数器的值传递给Sync实例,具体如下:publicCountDownLatch(intcount){if(count<0)throw新的IllegalArgumentException(“计数<0”);this.sync=newSync(count);}在countDown方法中,只调用了Sync实例的releaseShared方法,具体如下:publicvoidcountDown(){sync.releaseShared(1);}releaseShared方法,首先将计数器减1,如果减1后的计数器为0,则唤醒所有被await方法阻塞的线程,具体是这样的:publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){//将计数器减1doReleaseShared();//如果计数器为0,唤醒所有被await方法阻塞的线程returntrue;}returnfalse;}tryReleaseShared方法首先获取当前计数器的值,如果计数器为0,如果不为0,则使用CAS方法将计数器减1,具体是这样的:protectedbooleantryReleaseShared(intreleases){for(;;){//无限循环,如果CAS操作失败会继续尝试。intc=getState();//获取当前计数器的值。if(c==0)//当计数器为0时,直接返回。返回假;intnextc=c-1;if(compareAndSetState(c,nextc))//使用CAS方法将计数器减1returnnextc==0;//如果操作成功,返回计数器是否为0}}在await方法中,只有调用Sync实例的acquireSharedInterruptibly方法,具体如下:publicvoidawait()throwsInterruptedException{sync.acquireSharedInterruptibly(1);}其中acquireSharedInterruptibly方法判断计数器是否为0,不为0则阻塞当前线程具体是这样的:if(tryAcquireShared(arg)<0)//判断计数器是否为0doAcquireSharedInterruptibly(arg);//不为0则阻塞当前线程}其中tryAcquireShared方法是AbstractQueuedSynchronizer中的一个模板方法,其具体实现在Sync类中。主要判断计数器是否为零,为零则返回1,不为零则返回-1,具体是这样的:protectedinttryAcquireShared(intacquires){return(getState()==0)?1:-1;}面试官:嗯,很好,我马上给你发offer。本故事纯属虚构,如有雷同纯属巧合

猜你喜欢