面试官跟我扯了半小时 CountDownLatch 后,给我发 Offer?| 原力计划-程序员宅基地

作者 | 万猫学社
责编 | 王晓曼
出品 | 程序员宅基地

一个长头发、穿着清爽的小姐姐,拿着一个崭新的Mac笔记本向我走来,看着来势汹汹,我心想着肯定是技术大佬吧!但是我也是一个才华横溢的人,稳住我们能赢。


面试官:看你简历上有写熟悉并发编程,CountDownLatch一定用过吧,跟我说说它!

:CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。

面试官:CountDownLatch有哪些常用的方法?

:有CountDown方法和Await方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用CountDown方法时,计数器会被减1;当调用Await方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被CountDown方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用Await方法都会直接返回。

面试官调用CountDown方法时,线程也会阻塞嘛?

:不会的,调用CountDown的线程可以继续执行,不需要等待计数器被减到0,只是调用Await方法的线程需要等待。

面试官:可以举一个使用CountDownLatch的例子吗?

:比如张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。

面试官:可以写一下吗?

我:当然可以,这是张三、李四和王五的顾客类:

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

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

:可以使用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实例,具体是这样的:

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。

版权声明:本文为CSDN博主「万猫学社」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/heihaozi/article/details/105738230

【END】

更多精彩推荐
  ☞芯片供应被掐断,华为能否安全渡劫?
☞OceanBase 十年:一群追梦人的成长史
☞2 年 6 个月 11 天,外包到阿里的修仙之路!| 原力计划
☞服务器软件大扫盲!
☞绝悟之后再超神,腾讯30篇论文入选AI顶会ACL
☞中本聪并没有出现,那真相是?
你点的每个“在看”,我都认真当成了喜欢
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/csdnnews/article/details/106324464

智能推荐

socket网络编程——读缓冲区、写缓冲区怎么区分?_读缓冲区和写缓冲区-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏4次。一、读、写缓冲区我们使用socket进行网络通信时,通过调用socket()函数创建一个文件描述符fd进行通信。而socket所创建的文件描述符fd拥有两块内存缓冲区:读缓冲区/接收缓冲区写缓冲区/发送缓冲区二、如何区分问题是怎么记忆区分这两个缓冲区呢????️?我们举一个例子????:服务端sfd给客户端cfd发送数据。1、服务端调用write()把数据写进发送缓冲区,也就是写缓冲区了。2、内核读取写缓冲区数据后发送给客户端接收缓冲区,也就是读缓冲区了。3、客户端调用read()_读缓冲区和写缓冲区

SystemUI 状态栏增加移动数据开启图标_system ui 下拉添加 移动数据图标-程序员宅基地

文章浏览阅读3.9k次,点赞3次,收藏5次。原生设计中,移动数据图标只有在网络活动(下载/上传)时,才显示相应的小白色三角图标,如果没有网络活动则没有任何显示。需要在不活动时也显示灰色的三角图标。系统导航栏中常见信号图标包括:SIM卡信号(移动数据图标)、WIFI。主要关注几个文件网络监听控制NetworkControllerImpl.java信号变化控制 MobileSignalController.java WifiSignalController.java图标显示view StatusBarMobileView.java._system ui 下拉添加 移动数据图标

数据结构:栈-C语言实现_函数void printstack(stack s)的功能是打印栈中全部元素,请在顺序和链接两种-程序员宅基地

文章浏览阅读2k次,点赞14次,收藏15次。数据结构:栈-C语言实现,图文详讲,有注解和完整的代码_函数void printstack(stack s)的功能是打印栈中全部元素,请在顺序和链接两种

2016年华为秋招机试题——1.回文数字判断(100分)_设计一个函数,判断其unsigned int 型参数值是否是回文(要求用数组实现)。编写测试-程序员宅基地

文章浏览阅读1.6k次。回文数字判断 描述:有这样一类数字,他们顺着看和倒着看是相同的数,例如:121,656,2332等,这样的数字就称为:回文数字。判断某数字是否是回文数字。 运行时间限制:10Sec 内存限制:128MByte 输入:整型数字 输出:0:不是回文数字;1:是回文数字。 样例输入:121 样例输出:1_设计一个函数,判断其unsigned int 型参数值是否是回文(要求用数组实现)。编写测试

Linux学习之正则表达式元字符和grep命令_linux 元字符集合-程序员宅基地

文章浏览阅读1.1k次。正则表达式是一种搜索字符串的模式,通俗点理解,也就是普通字符和元字符共同组成的字符集合匹配模式。正则表达式的主要作用是文本搜索和字符串处理。元字符就是一些具有特殊含义的字符,用于表示某种特定的字符类型或者行为,它的含义就不是显示在计算机上的含义了。此文章为8月Day 4学习笔记,内容来源于极客时间。看到操作系统的版本是。_linux 元字符集合

第十一周项目 输出顶点出度,最大顶点编号,出度0的顶点和指定边_求图中出度最大的顶点编号-程序员宅基地

文章浏览阅读452次。问题及代码/**Copyright(c)2017,烟台大学计算机学院*All right reserved.*文件名:main.cpp btree.h btree.cpp*作者:王万兴*完成日期:2017年11月16日*版本号:v1.0**问题描述:邻接表问题*输入描述:无*程序输出:测试结果*/#include #include #include "grap_求图中出度最大的顶点编号

随便推点

java.lang.AbstractMethodError: Receiver class org.springframework.cloud.netflix.ribbon.RibbonLoadBal-程序员宅基地

文章浏览阅读4.5k次,点赞3次,收藏7次。java.lang.AbstractMethodError: Receiver class org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient does not define or inherit an implementation of the resolved method 'abstract org.springframework.cloud.client.ServiceInstance choose(java.lang._receiver class org.springframework.cloud.netflix.ribbon.ribbonloadbalancercl

Spring --单元测试及使用logback打印测试结果_单元测试 mockito ch.qos.logback.core.joran.spi.joranex-程序员宅基地

文章浏览阅读2k次。(1)将logback集成到junit中package com.liutao.utils;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.joran.JoranConfigurator;import ch.qos.logback.core.joran.spi.JoranException_单元测试 mockito ch.qos.logback.core.joran.spi.joranexception

搭建Spark所遇过的坑-程序员宅基地

文章浏览阅读252次。一.经验1.Spark Streaming包含三种计算模式:nonstate .stateful .window2.kafka可通过配置文件使用自带的zookeeper集群3.Spark一切操作归根结底是对RDD的操作4.部署Spark任务,不用拷贝整个架包,只需拷贝被修改的文件,然后在目标服务器上编译打包。5.kafka的log.dirs不要设置成/t..._spark节点启动报错 ,invalid master url

Maven3.5.0下载与配置-程序员宅基地

文章浏览阅读2.2w次,点赞9次,收藏16次。1.Maven3.5.0下载Maven3.5.0下载地址如果的Windows版点击apache-maven-3.5.0-bin.zip进行下载2.Maven3.5.0安装步骤与环境变量配置Maven3.5.0运行需要两个环境变量JAVA_HOME:JDK的环境变量。M2_HOME:Maven3.5.0的安装路径。JAVA_HOME之前已经配置完成。Mav_maven3.5.0下载

工业控制(ICS)---IEC60870-程序员宅基地

文章浏览阅读219次,点赞3次,收藏7次。(((iec60870_asdu && tcp.stream == 0)) && (iec60870_asdu.normval)) && (iec60870_asdu.typeid == 34)发现IOA的值对应的基本都是乱码,或者发现还有三百多条数据判断他是正常的。发现有些数据包是报错的,正确的包应该含有IOA的值iec60870_asdu && tcp.stream == 0 && iec60870_asdu.normval。IEC104(101的网络版)可尝试用type进行分类。

批量学习(batch learning)和在线学习(online learning)-程序员宅基地

文章浏览阅读1.3w次。批量学习(batch learning),一次性批量输入给学习算法,可以被形象的称为填鸭式学习。在线学习(online learning),按照顺序,循序的学习,不断的去修正模型,进行优化。batch越小,训练完一组的时间越短,但可能需要更多的步数接近局部最佳值,从大体效果来说,batch对结果影响应该不大。http://blog.csdn.net/vividonly/article/detail_batch learning

推荐文章

热门文章

相关标签