求求大厂给个Offer:多线程基础面试题

时间:2022-07-24
本文章向大家介绍求求大厂给个Offer:多线程基础面试题,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这是三歪第393篇原创 《求求大厂给个Offer》连载第四篇

我,三歪,最近开始写面试系列。我给这个面试系列取了一个名字,叫做《求求大厂给个Offer

前面已经写了三篇了,主要的内容包含集合和简历,喜欢看的同学在微信公众号下回复「面试」即可观看。

所以这篇文章叫做《求求大厂给个Offer:多线程基础面试题

接下来就开始吧。

面试现场

面试官:“听说你上次还差一点点在看数才及格,但你的表现我觉得还是可以的,破格再来面试一轮呗,这次我们来聊聊多线程吧。”

三歪:“嗯嗯,大多数人都还是比较喜欢看的,谢谢面试官给予我多一次的机会。”

面试官:“首先你来讲讲进程和线程的区别吧?”

三歪:“进程是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源。进程实现多处理机环境下的进程调度,分派,切换时,都需要花费较大的时间和空间开销。为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理,所以有了线程,线程取代了进程了调度的基本功能”

三歪:“简单来说:进程作为资源分配的基本单位,线程作为资源调度的基本单位”

面试官:“那我们为什么要用多线程呢?你平时工作中用得多吗?”

三歪:“使用多线程最主要的原因是提高系统的资源利用率。现在CPU基本都是多核的,如果你只用单线程,那就是只用到了一个核心,其他的核心就相当于空闲在那里了。

三歪:“在平时工作中多线程是随时都可见的。比如说,我们系统Web服务器用的是Tomcat,Tomcat处理每一个请求都会从线程连接池里边用一个线程去处理。又比如说,我们用连接数据库会用对应的连接池,比如Druid/C3P0/DBCP等等,这些都用了多线程的。”

三歪:“除了上面这些框架已经帮我们屏蔽掉「手写」多线程的问题,在我本身的系统也会用到多线程的。比如说:现在要跑一个定时任务,该任务的链路执行时间和过程都非常长,我们这边就用一个线程池将该定时任务的请求进行处理,这样做的好处就是可以及时返回结果给调用方,能够提高系统的吞吐量。“

// 请求直接交给线程池来处理
public void push(PushParam pushParam) {
  try {
    pushServiceThreadExecutor.submit(() -> {
      handler(pushParam);
    });
  } catch (Exception e) {
    logger.error("pushServiceThreadExecutor error, exception{}:", e);
  }
}

三歪:”还有就是我的系统中用了很多生产者与消费者模式,会用多个线程去消费队列的消息,来提高并发度“

面试官:”要不你来讲讲什么是线程安全?“

三歪:”在我的理解下,在Java世界里边,所谓线程安全就是多个线程去执行某类,这个类始终能表现出正确的行为,那么这个类就是线程安全的。比如我有一个count变量,在service方法不断的累加这个count变量。

public class UnsafeCountingServlet extends GenericServlet implements Servlet {
    private long count = 0;

    public long getCount() {
        return count;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

        ++count;
        // To something else...
    }
}

三歪:”假设相同的条件下,count变量每次执行的结果都是相同,那我们就可以说是线程安全的。显然上面的代码肯定不是线程安全的。“

三歪:”只要用到多线程,我们肯定得考虑线程安全的问题。“

面试官:”那你是怎么解决线程安全问题的呢?“

三歪:”其实大部分时间我们在代码里边都没有显式去处理线程安全问题,因为这大部分都由框架所做了。正如上面提到的Tomcat、Druid、SpringMVC等等。“

三歪:”很多时候,我们判断是否要处理线程安全问题,就看有没有多个线程同时访问一个共享变量。像SpringMVC这种,我们日常开发时,不涉及到操作同一个成员变量,那我们就很少需要考虑线程安全问题。我个人解决线程安全问题的思路有以下:“

  • 能不能保证操作的原子性,考虑atomic包下的类够不够我们使用。
  • 能不能保证操作的可见性,考虑volatile关键字够不够我们使用
  • 如果涉及到对线程的控制(比如一次能使用多少个线程,当前线程触发的条件是否依赖其他线程的结果),考虑CountDownLatch/Semaphore等等。
  • 如果是集合,考虑java.util.concurrent包下的集合类。
  • 如果synchronized无法满足,考虑lock包下的类
  • ....

三歪:”总的来说,就是先判断有没有线程安全问题,如果存在则根据具体的情况去判断使用什么方式去处理线程安全的问题。虽然synchronized很牛逼,但无脑使用synchronized会影响我们程序的性能的。“

面试官:”死锁你了解吗?什么情况会造成死锁?要是你能给我讲清楚死锁,我就录取你了

三歪:”要是你录取我,我就给你讲清楚死锁

面试官&三歪:”......“

三歪:”造成死锁的原因可以简单概括为:当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资源,都不放弃自己拥有的资源。避免死锁的方式一般有以下方案:

  1. 固定加锁的顺序“,比如我们可以使用Hash值的大小来确定加锁的先后
  2. 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁。
  3. 使用可释放的定时锁(一段时间申请不到锁的权限了,直接释放掉)

三歪:”面试官,你觉得我讲得怎么样?“

面试官:”说实话,还是差点东西。多线程显然不单单这么点东西,你也别想着这么简单我就放你通过了,不过今天的面试时间也到这里就结束了。这样吧,你这次要是能拿到100个在看,我再约你下一轮,再好好面你Java锁相关的知识,这次没有100个在看,面试就结束了。“