本文 首发于 🍀 永浩转载 请注明 来源

5、【对线面试官】多线程基础

首先你来讲讲进程和线程的区别吧?

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

那我们为什么要用多线程呢?你平时工作中用得多吗?

  1. 使用多线程最主要的原因是提高系统的资源利用率。
  2. 现在CPU基本都是多核的,如果你只用单线程,那就是只用到了一个核心,其他的核心就相当于空闲在那里了。
  3. 在平时工作中多线程是随时都可见的。
  4. 比如说,我们系统Web服务器用的是Tomcat,Tomcat处理每一个请求都会从线程连接池里边用一个线程去处理。
  5. 又比如说,我们用连接数据库会用对应的连接池Druid/C3P0/DBCP等等
  6. 等等这些都用了多线程的。
  7. 上面这些框架已经帮我们屏蔽掉「手写」多线程的问题

嗯,了解,那你实际开发中有用过吗?

  1. 当然有了,在我所负责的系统也会用到多线程的。

  2. 比如说,现在要跑一个定时任务,该任务的链路执行时间和过程都非常长,我这边就用一个线程池将该定时任务的请求进行处理。

  3. 这样做的好处就是可以及时返回结果给调用方,能够提高系统的吞吐量。

    // 请求直接交给线程池来处理
    public void push(PushParam pushParam) {
      try {
        pushServiceThreadExecutor.submit(() -> {
          handler(pushParam);
        });
      } catch (Exception e) {
        logger.error("pushServiceThreadExecutor error, exception{}:", e);
      }
    }
    
  4. 还有就是我的系统中用了很多生产者与消费者模式,会用多个线程去消费队列的消息,来提高并发度

你如果在项目中用到了多线程,那肯定得考虑线程安全的问题的吧

  1. 在我的理解下,在Java世界里边,所谓线程安全就是多个线程去执行某类,这个类始终能表现出正确的行为,那么这个类就是线程安全的。
  2. 比如我有一个count变量,在service方法不断的累加这个count变量。
  3. 假设相同的条件下,count变量每次执行的结果都是相同,那我们就可以说是线程安全的干
  4. 显然下面的代码肯定不是线程安全的
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...
    }
}

那你平时是怎么解决,或者怎么思考线程安全问题的呢?

  1. 其实大部分时间我们在代码里边都没有显式去处理线程安全问题,因为这大部分都由框架所做了。
  2. 正如上面提到的Tomcat、Druid、SpringMVC等等。
  3. 很多时候,我们判断是否要处理线程安全问题,就看有没有多个线程同时访问一个共享变量。
  4. 像SpringMVC这种,我们日常开发时,不涉及到操作同一个成员变量,那我们就很少需要考虑线程安全问题。
  5. 我个人解决线程安全问题的思路有以下:
    • 能不能保证操作的原子性,考虑atomi c包下的类够不够我们使用。
    • 能不能保证操作的可见性,考虑volatil e关键字够不够我们使用
    • 如果涉及到对线程的控制(比如一次能使用多少个线程,当前线程触发的条件是否依赖其他线程的结果),考虑CountDownLatch/Semaphore等等。
    • 如果是集合,考虑java.util.concurrent 包下的集合类。
    • 如果synchronized无法满足,考虑lock 包下的类
  6. 总的来说,就是先判断有没有线程安全问题,如果存在则根据具体的情况去判断使用什么方式去处理线程安全的问题。
  7. 虽然synchronized很牛逼,但无脑使用synchronized会影响我们程序的性能的。

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

  1. 要是你录取我,我就给你讲清楚死锁
  2. 造成死锁的原因可以简单概括为:当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资源,都不放弃自己拥有的资源。
  3. 避免死锁的方式一般有以下方案:
    • 固定加锁的顺序,比如我们可以使用Hash值的大小来确定加锁的先后
    • 尽可能缩减加锁的范围,等到操作共享变量的时候才加锁。
    • 使用可释放的定时锁(一段时间申请不到锁的权限了,直接释放掉)

价值体现

嗯,其实我想问,就是我要是..去到贵公司是做什么内容?还有就是..