当前位置: 首页 > 科技观察

锁和栅栏的区别及其适用场景

时间:2023-03-21 14:59:55 科技观察

文章开头,相信小伙伴们都或多或少的熟悉这两个名词。它们是并发编程中常用的线程通信工具。两者很相似,但又有不同,这让包括我在内的很多朋友产生了很大的困惑:两者有什么区别,分别适用于哪些场景?慢慢听我说,不想看例子或者流程的小伙伴可以拉到最下面看总结。CountDownLatch(CountDownLatch)俗称计数器,官方(谷歌机器翻译,哈哈)解释:/***Asynchronizationaidthatallowsoneormorethreadstowaituntil*asetofoperationsbeingperformedinotherthreadscompletes.*/Allowsoneormultiplethreadstowait,一个等待直到一组在其他线程中执行的操作完成。大致意思是可以有一个或多个线程,等待其他线程完成某项操作后再继续执行。这是什么意思?举个例子:生活中应该经常遇到这样的情况。在乘坐公交车时,尤其是在始发站,司机往往会等到公交车上的乘客达到一定数量,以便一次吸引更多的乘客。稍后开始。测试代码如下:publicstaticvoidmain(String[]args){Listlist=newArrayList<>();Passengerp1=newPassenger("Readthebook");Passengerp2=newPassenger("Checkthephone");Passengerp3=newPassenger("看风景");Passengerp4=newPassenger("看售票员");list.add(p1);list.add(p2);list.add(p3);list.add(p4);ThreadPoolExecutorexecutor=newThreadPoolExecutor(20,200,1000,TimeUnit.SECONDS,newLinkedBlockingQueue<>(3),newThreadFactory(){privateThreadGroupgroup=(null==System.getSecurityManager()?Thread.currentThread().getThreadGroup():System.getSecurityManager().getThreadGroup());privateAtomicIntegernum=newAtomicInteger();@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(group,r,"zoo"+num.getAndIncrement(),0);thread.setDaemon(false);returnthread;}},newThreadPoolExecutor.CallerRunsPolicy());//设置latch释放阈值g车,等人...");for(Passengerp:list){executor.execute(()->gotoZOO(p,countDownLatch));}try{countDownLatch.await();log.error("人够了,起飞!");executor.shutdown();}catch(InterruptedExceptione){e.printStackTrace();}}privatestaticvoidgotoZOO(Passengerp,CountDownLatchcountDownLatch){log.error("{}的乘客上车了",p.getDoWhat());try{countDownLatch.countDown();日志。error("{}",p.doWhatOnBus());}catch(Exceptione){e.printStackTrace();}}staticclassPassenger{privateStringdoWhat;publicPassenger(StringdoWhat){this.doWhat=doWhat;}publicStringgetDoWhat(){returndoWhat;}publicStringdoWhatOnBus(){return"公交车好无聊,"+doWhat+"走吧!";}}执行结果23:46:34.698[main]ERRORcom.test-司机坐满人,然后启动公交车,等会...23:46:34.757[zoo1]ERRORcom.test-会用手机的乘客上车23:46:34.758[zoo3]ERRORcom.test-会懂乘务员的乘客上车23:46:34.757[zoo0]ERRORcom.test-会看书的乘客上车23:46:34.759[zoo1]ERRORcom.test-公交车好无聊,我们看手机吧!23:46:34.759[zoo3]ERRORcom.test-公交车好无聊,找售票员!23:46:34.757[zoo2]ERRORcom.test-可以欣赏风景的乘客上车23:46:34.759[zoo0]ERRORcom.test-在车里好无聊,我们看看书吧!23:46:34.759[zoo2]ERRORcom.test-坐车好无聊,看看风景吧!23:46:34.759[main]ERRORcom.test-人够了,起飞!drivermaster(主线程)会等到4个乘客才启动汽车(等4个子线程完成一些事情调用countDown方法),乘客上车后自己做自己的事情(调用countDown).事情,你不会因为上了车就傻傻的什么都不做(你不会因为调用countDown而挡住自己)等待司机师傅看到足够多的人(达到设定的阈值),然后开始车。阻塞总结:主线程调用await后,会阻塞等待其他子线程调用countDown方法将设置的阈值降为0,然后继续执行。而子线程序不会因为调用了countDown方法而阻挡栅栏栅栏(CyclicBarrier)官方解释:/***Asynchronizationaidthatallowsasetofthreadstoallwaitfor*eachothertoreachacommonbarrierpoint.CyclicBarriers*usefulinprogramsinvolvingafixedsizedpartyofthreadsthat*mustoccasionallywaitforeachother.Thebarrieriscalled*becauseit'sbecauseitcalled*becausecyclicrereleaseed*/同步帮助,允许一组线程互相等待到达一个共同的障碍点。CyclicBarriers在涉及有时必须相互等待的固定大小线程的程序中很有用。这个屏障被称为循环,因为它可以在等待线程被释放后被重用。从类注解中,我们可以大致理解为在一个组中使用,即多线程。当所有线程都达到某个状态后,就会阻塞,直到所有线程都达到某个状态后才继续执行。而且它是可重复使用的。上面的描述还是太笼统了,举个栗子:小时候学校组织春游,地点定了,大家都来了就一起进去玩。我写了一个简单的例子来查看围栏在这个场景中是如何工作的("看猩猩");Boyboy3=newBoy("看狮子");Boyboy4=newBoy("看指挥");list.add(boy1);list.add(boy2);list.add(boy3);list.add(boy4);ThreadPoolExecutorexecutor=newThreadPoolExecutor(20,200,1000,TimeUnit.SECONDS,newLinkedBlockingQueue<>(3),newThreadFactory(){privateThreadGroupgroup=(null==System.getSecurityManager()?Thread.currentThread().getThreadGroup():System.getSecurityManager().getThreadGroup());privateAtomicIntegernum=newAtomicInteger();@OverridepublicThreadnewThread(Runnabler){Threadthread=newThread(group,r,"zoo"+num.getAndIncrement(),0);thread.setDaemon(false);returnthread;}??},newThreadPoolExecutor.CallerRunsPolicy());//初始化栅栏,设置屏障点阈值CyclicBarriercyclicBarrier=newCyclicBarrier(list.size());for(Boyboy:list){executor.执行(()->去toZOO(boy,cyclicBarrier));}}privatestaticvoidgotoZOO(Boyboy,CyclicBarriercyclicBarrier){log.error("大家还没到,稍等,{}里的小男孩在等",boy.getWhere());try{cyclicBarrier.await();log.error("{}",boy.goWhere());}catch(Exceptione){e.printStackTrace();}}staticclassBoy{privateStringwhere;publicBoy(Stringwhere){this.where=where;}publicStringgetWhere(){returnwhere;}publicStringgoWhere(){return"人都到齐了,我去"+where+"!";}}执行结果:22:05:59.476[zoo2]ERRORcom.test-人还没到,等一下,看狮子的小男孩开始等待22:05:59.477[zoo1]ERRORcom.test-peoplearenothere,等一下,看猩猩的小男孩开始等待22:05:59.477[zoo0]ERRORcom.test-people还没到,稍等,看虎的小男孩开始等待22:05:59.476[zoo3]ERRORcom.test-人还没到,等一下,小男孩看着售票员开始等待22:05:59.484[zoo0]ERRORcom.test-Everyone来了,我要去看老虎!22:05:59.484[zoo2]ERRORcom.test-大家都在,我要去看狮子!22:05:59.484[zoo3]ERRORcom.test-大家都到了,我要见指挥!22:05:59.484[zoo1]ERRORcom.test-大家都在,我要去看猩猩!我们可以发现前三个小男孩到了之后并没有进入动物园,但是四个小男孩直到第四个小男孩才进入动物园。在此之前,每来一个孩子,就多一个孩子等待(每个线程调用await方法),直到所有人都到(线程阻塞等待到达障碍点4),然后每个小男孩继续进入动物园看动物(每个线程继续执行自己的任务)就像动物园门口的围栏。如果买的是团票,就得让所有的人让孩子进去。fence总结一下,fence初始化的时候各个子线程互相等待,直到达到阈值,然后继续进行微分和个人对加锁的理解:有点类似于统计函数(也许这就是为什么俗称计数器),主线程调用await方法阻塞等待统计结果,子线程只负责用于在满足统计要求时调用countDown方法告诉主线程我OK了,不会阻塞自己;有一个负责接收结果(主线程)和一个或多个发送数(子线程);fence:首先,当线程调用await方法时,当前线程会被阻塞。其次,我个人的理解是,它并没有像锁一样的主子关系。就是各个线程互相等待,都到了某个点,就继续执行。从上面的区分其实可以看出适用场景:如果需要将多线程执行是否完成的接口归纳到某个线程中,然后继续执行,比如每个线程计算一个指标,并且计算完成后计算出所有指标或者其他的总和,就可以使用locking了;而如果每个线程都需要等待每个线程完成后再继续自己的业务,可以使用fence,比如ABC三个线程分别获取123三个指标,然后A需要对这三个数取平均值,B需要取和,C需要取方差。然后需要等ABC取完123的三个指标再计算。这时候就可以使用围栏了。综上所述,两者都是非常不错的线程通信工具,但细节还是有区别的。总的来说:加锁就是等待某个线程获取其他线程的执行结果;而barrier就是线程之间互相等待,然后同时开始做自己的事情。上一篇的代码只是为了更好的说明两者。两个工具的区别,如果写的不好,还请多多包涵。如果您发现不对的地方,欢迎您留言,我们共同进步!最后,如果你觉得文章不错,不妨动动你的双手。点个赞就去吧,下次还不确定~