01 面试题系列 | 线程池相关面试题

线程池

线程池作用
  1. 降低资源消耗:通过重复利用已经创建的线程来降低线程创建和销毁造成的消耗;
  2. 提高响应速度:因为已经提前创建好了线程,无需在等待线程重新创建就可以立即执行任务;
  3. 提高了线程的可管理性:线程属于稀缺性资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低系统的稳定性,线程池可以实现对线程的统一分配、调用和监控。
线程池主要实现类

线程池都继承了 ExecutorService 的接口,所以他们都具有 ExecutorService 的生命周期方法:运行,关闭,终止;因为继承了 ExecutorService 接口,所以它在被创建的时候就是处于运行状态,当线程没有任务执行时,就会进入关闭状态,只有调用了 shutdown()的时候才是正式的终止了这个线程池。

常见四种线程池:

  • newFixedThreadPool

    固定大小的线程池,可以指定线程池的大小,该线程池 corePoolSize 和 maximumPoolSize 相等,阻塞队列使用的是 LinkedBlockingQueue,大小为整数最大值。该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的 LinkedBlockingQueue 来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue 迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要 shutdown。

  • newSingleThreadExecutor

    单个线程线程池,只有一个线程的线程池,阻塞队列使用的是 LinkedBlockingQueue, 若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

  • newCachedThreadPool

    缓存线程池,缓存的线程默认存活 60 秒。线程的核心池 corePoolSize 大小为0,核心池最大为Integer.MAX_VALUE, 阻塞队列使用的是 SynchronousQueue。是一个直接提交的阻塞队列,他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过 keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

  • newScheduledThreadPool

    定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

    scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

    schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

Java 通过 Executors 工厂类提供的线程池一共有4种:

  • ThreadPoolExecutor() // 指定线程数的线程池

  • fixedThreadPool() // 启动固定线程数的线程池

  • CachedThreadPool() // 按需分配的线程池

  • ScheduledThreadPoolExecutor() // 定时,定期执行任务的线程池

ThreadPoolExecutor

启动指定数量线程以及将任务添加到队列,并且将任务发送给空闲的线程

常用构造函数如下:

public ThreadPoolExecutor(
    // 核心线程数,除非 allowCoreThreadTimeOut 被设置为 true,否则它闲着也不会死
	int corePoolSize, 
	// 最大线程数,活动线程数量超过它,后续任务就会排队                   
	int maximumPoolSize, 
	// 超时时长,作用于非核心线程(allowCoreThreadTimeOut 被设置为 true 时也会同时作用于核心线程),闲置超时便被回收           
	long keepAliveTime,                          
	// 枚举类型,设置 keepAliveTime 的单位,有 TimeUnit.MILLISECONDS(ms)、TimeUnit. SECONDS(s)等
	TimeUnit unit,
	// 缓冲任务队列,线程池的 execute 方法会将 Runnable 对象存储起来
	BlockingQueue<Runnable> workQueue,
	//线程工厂接口,只有一个 new Thread(Runnable r) 方法,可为线程池创建新线程
	ThreadFactory threadFactory,
    // 任务被拒绝后调用的handler
	RejectedExecutionHandler handler)
}
  1. 当线程池小于 corePoolSize 时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程 ;

  2. 当线程池达到 corePoolSize 时,新提交任务将被放入 workQueue 中,等待线程池中任务调度执行 ;

  3. 当 workQueue 已满,且 maximumPoolSize > corePoolSize 时,新提交任务会创建新线程执行任务 ;

  4. 当提交任务数超过 maximumPoolSize 时,新提交任务由 RejectedExecutionHandler 处理 ;

  5. 当线程池中超过 corePoolSize 线程,空闲时间达到 keepAliveTime 时,关闭空闲线程 ;

  6. 当设置 allowCoreThreadTimeOut(true) 时,线程池中 corePoolSize 线程空闲时间达到 keepAliveTime 也将关闭 ;

Executor 拒绝策略说的是什么?

当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,拒绝策略有四种:

  • ThreadPoolExecutor.AbortPolicy : 丢弃任务并抛出 RejectedExecutionException 异常。
  • ThreadPoolExecutor.DiscardPolicy :也是丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy :丢弃队列最前面的任务,执行后面的任务
  • ThreadPoolExecutor.CallerRunsPolicy :由调用线程处理该任务
无界阻塞延迟队列 Delayqueue 原理是什么?

DelayQueue 是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。

使用场景如下:
  • 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
  • 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。
  • 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求。
基本原理:

首先,这种队列中只能存放实现 Delayed 接口的对象,此接口有两个需要实现的方法,其中 getDelay() 这个方法需要返回对象过期前的时间。简单说,队列在某些方法处理前,会调用此方法来判断对象有没有超时。

其次,DelayQueue 是一个 BlockingQueue,其特化的参数是 Delayed。

Delayed 扩展了 Comparable 接口,比较的基准为延时的时间值,Delayed 接口的实现类 getDelay() 的返回值应为固定值(final)。

DelayQueue 内部是使用 PriorityQueue 实现的。

CyclicBarrier 和 CountDownLatch 的区别?
CountDownLatch

Countdownlatch是一个同步工具类;用来协调多个线程之间的同步;

这个工具通常用来控制线程等待;它可以让某一个线程等待知道倒计时结束,在开始执行;

CountDownLatch 的两种用法

参考 :https://www.cnblogs.com/yjxs/p/9911903.html

  1. 某一线程在开始运行前等待 n 个线程执行完毕

    将 CountDownLatch 的计数器初始化为 n , new CountDownLatch(n) , 每当一个任务线程执行完毕,就将计数器减 1,CountDownLatch.Countdown;当计数器的值变为 0 时,在 CountDownLatch 上 await() 的线程就会被唤醒。

    一个典型应用场景就是在启动一个服务时,主线程需要等待多个组件加载完毕;

  2. 实现多个线程开始执行任务的最大并行性。注意是并行性,而不是并发性,强调的是多个线程在某一时刻同时执行,类似于赛跑,将多个线程放到起点,然后等待发令枪响,最后同时开跑。

    做法是初始化一个共享的 CountDownLatch 对象,将其计数器初始化为1(new CountdownLatch(1));多个线程在开始执行任务前首先 CountDownLatch.await(), 当主线程调用 countdownl 时,计数器变为0,多个线程同时被唤醒。

CountDownLatch 的不足

Countdownlatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,他不能再次被使用。

CyclicBarrier

CyclicBarrier 和 CountDownLatch非常类似,他也可以实现线程间的技术等待,但是它的功能比CountDownLatch 更加强大和复杂。主要应用场景和 CountDownLatch 类似

CyclicBarrier 的字面意思是可循环使用的屏障,它要做的事情是,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

CyclicBarrier 默认的构造方法时 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier 的应用场景

CyclicBarrier 可以用于多线程计算数据,最后合并结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个 Sheet 保存每一个账户近一年的每一笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,使用 barrierAction 用这些线程的计算结果,计算出整个 Excel 的日均银行流水;

CyclicBarrier(int parties,Runnable BarrierAction),在线程到达屏障时,优先执行 BarrierAction,方便处理更复杂的业务场景。

小结
CountDownLatchCyclicBarrier
减计数方式加计数方式
计算为 0 时释放所有等待的线程计数达到指定值时释放所有等待线程
计数为 0 时,无法重置计数达到指定值时,计数置为 0 重新开始
调用 countDown() 方法计数减 1,调用 await() 方法只进行阻塞,对计数没有任何影响调用 await() 方法计数加 1,若 加 1 后的值不等于构造方法的值,则线程阻塞
不可重复利用可以重复利用
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值