线程池面试必考

你对Java线程池了解吗?你有用过线程池吗?那先说下线程池核心参数吧。。。对不起,我回去再看看吧。

image.png

为了一丝体面,我们今天来整理几个面试中常考线程池面试问题吧!

 

为什么要用线程池?


  1. 线程复用。线程的重复使用是线程池设计的重点,如果需要开启1000个线程执行程序,系统会创建1000个线程,如果用线程池来执行1000个任务,并不需要开启1000个线程,只需要设置corePoolSize核心线程大小数量,最大线程数量,队列大小即可重复利用线程置换任务,而且1000个线程切换效率并不低,也就是说线程越多效率不一定高。所以在多线程环境实际开发中我们推荐用多线程。
  2. 更好的管理线程。ThreadPoolExecutor可以控制线程数量,根据实际应用场景设置队列数量和饱和策略。

 

你说下线程池核心参数?


  • corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。
  • maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize
  • keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。
  • workQueue :阻塞队列。ArrayBlockingQueue,LinkedBlockingQueue等,用来存放线程任务。
  • defaultHandler :饱和策略。
  • ThreadFactory :线程工厂。新建线程工厂。

 

execute任务添加流程?


image.png

  1. 线程池执行execute/submit方法向线程池添加任务,当任务小于核心线程数corePoolSize,线程池中可以创建新的线程。
  2. 当任务大于核心线程数corePoolSize,就向阻塞队列添加任务。
  3. 如果阻塞队列已满,需要通过比较参数maximumPoolSize,在线程池创建新的线程,当线程数量大于maximumPoolSize,说明当前设置线程池中线程已经处理不了了,就会执行饱和策略。

 

 

饱和策略知道吗?


上图我们说过,当线程数量大于maximumPoolSize,就会执行饱和策略。ThreadPoolExecutor类中一共有4种饱和策略。通过实现RejectedExecutionHandler接口。

  • AbortPolicy : 线程任务丢弃报错。默认饱和策略。
  • DiscardPolicy : 线程任务直接丢弃不报错。
  • DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
  • CallerRunsPolicy :线程池之外的线程直接调用run方法执行。

 

下面我们在代码中看下饱和策略使用方式。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author :jiaolian
 * @date :Created in 2021-02-20 16:28
 * @description:线程池丢弃策略
 * @modified By:
 * 公众号:叫练
 */
public class AbortTest {

    //线程数量
    private static final int THREAD_COUNT = 50;

    private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(THREAD_COUNT);

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(1);

    //线程池丢弃策略
    public static void main(String[] args) throws InterruptedException {

        //新建一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,5,1, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(20),new ThreadPoolExecutor.AbortPolicy());

        //DiscardPolicy 丢弃
        //AbortPolicy 丢弃报错
        //DiscardOldestPolicy 将队列对首的任务丢弃,执行当前线程任务
        //CallerRunsPolicy 直接调用run方法

        //提交线程
        for (int i=0; i<THREAD_COUNT; i++) {
            threadPoolExecutor.execute(()->{
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" execute!"+ATOMIC_INTEGER.getAndIncrement());
                COUNT_DOWN_LATCH.countDown();
            });
        }

        COUNT_DOWN_LATCH.await();
        //关闭线程
        threadPoolExecutor.shutdown();


    }
}

如上代码:核心线程数量是3,最大线程数量是5,阻塞队列是20,共提交50个线程,这里饱和策略用的是AbortPolicy,分析执行线程池过程,线程池中首先开启3个核心线程Worker,发现3个线程处理不了50个线程任务,于是线程池就向阻塞队列添加任务,发现还是阻塞队列也容纳不下50个任务,于是又增加至2个线程同时运行线程任务,一共是5个线程同时运行任务,此时线程池中共有25个任务会被执行,还有25个任务会被丢弃,因为我们用的是AbortPolicy饱和策略,会报错,截部分图如下划红线所示。一共执行了25个任务。其他几种策略大家可以参照执行。

image.png

 

你平时线程池怎么用的?


  • Excutors.newSingleThreadExecutor :1个corePoolSize,LinkedBlockingQueue队列无限大,当创建无数个线程,队列无限长,可能出现OOM内存溢出。单一线程。
  • Executors.newCachedThreadPool :0个corePoolSize,Interger.MAX_VALUE最大线程数,当创建无数个线程,可能出现OOM内存溢出。适用小而多线程。
  • Executors.newFixedThreadPool :ncorePoolSize,n个最大线程个数,LinkedBlockingQueue阻塞队列,当创建无数个线程,队列无限长,可能出现OOM内存溢出。适用固定线程。
  • Executors.newScheduledThreadPool :ncorePoolSize,Interger.MAX_VALUE个最大线程数,当创建无数个线程,可能出现OOM内存溢出。

 

源码中线程池是怎么复用线程的?


源码中ThreadPoolExecutor中有个内置对象Worker,每个worker都是一个线程,worker线程数量和参数有关,每个worker会while死循环从阻塞队列中取数据,通过置换worker中Runnable对象,运行其run方法起到线程置换的效果,这样做的好处是避免多线程频繁线程切换,提高程序运行性能。

 

总结


今天我们介绍了线程池中面试中几个重要的面试点,整理出来希望能对你有帮助,写的比不全,同时还有许多需要修正的地方,希望亲们加以指正和点评,喜欢的请点赞加关注哦。点关注,不迷路,我是叫练【公众号】,微信号【jiaolian123abc】边叫边练。

原文地址:https://www.cnblogs.com/jiaolian/p/14434593.html