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

在头条采访中,和我聊了半个小时的Semaphore

时间:2023-04-01 18:31:13 Java

是一位留着长发、衣着整洁的小姐姐,拿着全新的Mac笔记本电脑来找我。看着来势汹汹的样子,我心想,她一定是个技术老大。酒吧!不过我也是个人才,坚持下去就能赢。面试官:简历上写着你熟悉并发编程。之前必须使用过信号量。告诉我怎么回事儿!我:Semaphore是JDK提供的一个同步工具,通过维护几个license来控制线程对共享资源的访问。如果剩余许可数大于零,则允许该线程访问共享资源;如果剩余许可数为零,则拒绝线程访问共享资源。Semaphore维护的许可数是允许访问共享资源的最大线程数。因此,想要访问共享资源的线程必须从Semaphore获得许可。面试官:Semaphore常用的方法有哪些?我:有acquire方式和release方式。当调用acquire方法时,线程会被阻塞,直到可以在Semaphore中获取license,然后线程才会去获取license。当调用release方法时,将向信号量添加许可证。如果线程因为获取许可而阻塞,则获取许可并释放;如果没有线程获取license,Semaphore只会记录license的可用性。数量。面试官:能举个使用Semaphore的例子吗?我:张三、李四、王五和赵六一起去餐厅吃饭,但是特殊时期洗手很重要,饭前洗手也是必须的,但是餐厅只有2个洗手池,并且sinks不能同时使用公共资源,这种场景可以使用Semaphore。面试官:能不能简单的用代码实现一下?我:当然,这是张三、李四、王五、赵六的客户类:packageonemore.study.semaphore;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.Random;导入java.util.concurrent.CountDownLatch;导入java.util.concurrent.Semaphore;公共类CustomerimplementsRunnable{privateSemaphorewashbasin;私有字符串名称;publicCustomer(Semaphorewashbasin,Stringname){this.washbasin=washbasin;这。名字=名字;}@Overridepublicvoidrun(){try{SimpleDateFormatsdf=newSimpleDateFormat("HH:mm:ss.SSS");随机random=newRandom();洗脸盆.acquire();系统输出。println(sdf.format(newDate())+""+name+"开始洗手...");Thread.sleep((long)(random.nextDouble()*5000)+2000);系统输出。println(sdf.format(newDate())+""+name+"洗手!");脸盆.release();}catch(Exceptione){e.printStackTrace();然后,写一个测试类来模拟洗手的过程:SemaphoreTester{publicstaticvoidmain(String[]args)throwsInterruptedException{//餐厅只用了两个sink,所以初始化license总数为2Semaphore洗脸盆=newSemaphore(2);列表<线程>threads=newArrayList<>(3);threads.add(newThread(newCustomer(洗脸盆,"张三")));threads.add(newThread(newCustomer(洗脸盆,"李四")));threads.add(newThread(newCustomer(洗脸盆,"王舞")));threads.add(newThread(newCustomer(洗脸盆,"赵刘")));for(Threadthread:threads){thread.start();线程.睡眠(50);}for(Threadthread:threads){thread.join();}}}运行后的结果应该是这样的:06:51:54.416李四开始洗手...06:51:54.416张三开始洗手...06:51:57.251张三洗完手手!06:51:57.251王舞开始洗手了...06:51:59.418李斯洗手完了!06:51:59.418赵六开始洗手了...06:52:02.496王舞洗手完了!06:52:06.162赵六洗手完了!可以看到两个人已经在洗手了,其他人都被挡住了,等到有人洗手完了才开始洗手。面试官:你对Semaphore的内部原理有了解吗?我:Semaphore主要是通过AQS(AbstractQueuedSynchronizer)来实现线程管理的。在构造Semaphore的时候,需要传入license的个数,最后传给AQS的state值。当一个线程调用acquire方法获取一个license时,如果Semaphore中的licenses数量大于0,则licenses数量减1,线程继续运行。当线程运行并调用release方法释放license时,licenses的数量会减少。加1,如果获取许可时Semaphore中的许可数为0,则获取失败,线程进入AQS的等待队列,等待其他释放许可的线程唤醒。面试官:嗯,还不错。在您的代码中,这4个人是否按照线程启动的顺序洗手?我:我不会按照线程启动的顺序洗手。有可能是赵六先于王五洗手。采访者:为什么会这样?我:因为在我的代码中,使用Semaphore的构造函数是这样的:publicSemaphore(intpermits){sync=newNonfairSync(permits);}在这个构造函数中,使用了NonfairSync(非公平锁),这个类不保证顺序线程在其中获得许可;调用acquire方法的线程可能会在等待的线程之前获得许可。采访者:有没有办法保证他们的订单?我:可以使用Semaphore的另一个构造函数:publicSemaphore(intpermits,booleanfair){sync=fair?newFairSync(permits):newNonfairSync(permits);}调用构造函数时,fair参数传入true,例如:Semaphorewashbasin=newSemaphore(2,true);这样通过FairSync(公平锁)保证按照各个线程调用acquire方法的先后顺序获取license。面试官:嗯,还不错。NonfairSync和FairSync有什么区别?为什么会有这样的效果?我:这涉及到NonfairSync和FairSync的内部实现。在NonfairSync中,acquire方法的核心源码是:finalintnonfairTryAcquireShared(intacquires){//acquires参数默认为1,表示尝试获取1个license。对于(;;){intavailable=getState();//remaining是剩余的licenses数量。intremaining=available-获取;//当剩余许可数小于0时,//当前线程进入AQS中的doAcquireSharedInterruptibly方法//等待可用的许可并挂起,直到被唤醒。if(remaining<0||compareAndSetState(available,remaining))返回剩余;}}release方法核心源码为:protectedfinalbooleantryReleaseShared(intreleases){//releases参数默认为1,表示尝试释放1个license。对于(;;){intcurrent=getState();//next是如果license释放成功的话可用license的数量。intnext=当前+版本;if(next