美文网首页Java
Java程序员去腾讯面试,居然扯了半小时的CountDownLa

Java程序员去腾讯面试,居然扯了半小时的CountDownLa

作者: 马小莫QAQ | 来源:发表于2020-09-10 20:50 被阅读0次

一个长头发、穿着清爽的小姐姐,拿着一个崭新的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.concurrent.CountDownLatch;

publicclassCustomerimplementsRunnable{

privateCountDownLatch latch;

privateString name;

publicCustomer(CountDownLatch latch, String name){

this.latch = latch;

this.name = name;

}@Override

publicvoidrun(){

try{

SimpleDateFormat sdf =newSimpleDateFormat("HH:mm:ss.SSS");

Random random =newRandom();

System.out.println(sdf.format(newDate()) +" "+ name +"出发去饭店");

Thread.sleep((long) (random.nextDouble() *3000) +1000);

System.out.println(sdf.format(newDate()) +" "+ name +"到了饭店");

latch.countDown();        }catch(Exception e) {

e.printStackTrace();        }    }}

这是服务员类:

packageonemore.study;

importjava.text.SimpleDateFormat;

importjava.util.Date;

importjava.util.concurrent.CountDownLatch;

publicclassWaitressimplementsRunnable{

privateCountDownLatch latch;

privateString name;

publicWaitress(CountDownLatch latch, String name){

this.latch = latch;

this.name = name;

}@Override

publicvoidrun(){

try{

SimpleDateFormat sdf =newSimpleDateFormat("HH:mm:ss.SSS");

System.out.println(sdf.format(newDate()) +" "+ name  +"等待顾客");

latch.await();            System.out.println(sdf.format(newDate()) +" "+ name  +"开始上菜");

}catch(Exception e) {

e.printStackTrace();        }    }}

然后,再写一个测试类,用于模拟上面所说的场景:

packageonemore.study;

importjava.util.ArrayList;

importjava.util.List;

importjava.util.concurrent.CountDownLatch;

publicclassCountDownLatchTester{

publicstaticvoidmain(String[] args)throwsInterruptedException{

CountDownLatch latch =newCountDownLatch(3);

List threads =newArrayList<>(3);

threads.add(newThread(newCustomer(latch,"张三")));

threads.add(newThread(newCustomer(latch,"李四")));

threads.add(newThread(newCustomer(latch,"王五")));

for(Thread thread : threads) {

thread.start();        }        Thread.sleep(100);

newThread(newWaitress(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方法才继续执行。

面试官:如果有一个顾客迟迟没到,饭店都打烊了,也不能一直等啊,应该这么办?

:可以使用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方法就继续执行开始上菜了。

面试官:CountDownLatch的实现原理是什么?

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

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

publicCountDownLatch(intcount){

if(count <0)thrownewIllegalArgumentException("count < 0");

this.sync =newSync(count);

}

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

publicvoidcountDown(){

sync.releaseShared(1);

}

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

publicfinalbooleanreleaseShared(intarg){

if(tryReleaseShared(arg)) {//对计数器进行减一操作

        doReleaseShared();//如果计数器为0,唤醒被await方法阻塞的所有线程

        return true;

    }

    return false;

}

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

protectedbooleantryReleaseShared(intreleases){

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方法,具体是这样的:

publicvoidawait()throwsInterruptedException{

sync.acquireSharedInterruptibly(1);

}

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

publicfinalvoidacquireSharedInterruptibly(intarg)

throwsInterruptedException{

if(Thread.interrupted())

thrownewInterruptedException();

if(tryAcquireShared(arg) <0)//判断计数器是否为0

        doAcquireSharedInterruptibly(arg);//如果不为0则阻塞当前线程

}

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

protectedinttryAcquireShared(intacquires){

return(getState() ==0) ?1:-1;

}

面试官:嗯,很不错,马上给你发offer。

来源:头条

原文地址:https://www.toutiao.com/i6826941953658061325/

相关文章

网友评论

    本文标题:Java程序员去腾讯面试,居然扯了半小时的CountDownLa

    本文链接:https://www.haomeiwen.com/subject/mckmektx.html