线程池
线程池作用
- 降低资源消耗:通过重复利用已经创建的线程来降低线程创建和销毁造成的消耗;
- 提高响应速度:因为已经提前创建好了线程,无需在等待线程重新创建就可以立即执行任务;
- 提高了线程的可管理性:线程属于稀缺性资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低系统的稳定性,线程池可以实现对线程的统一分配、调用和监控。
线程池主要实现类
线程池都继承了 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)
}
-
当线程池小于 corePoolSize 时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程 ;
-
当线程池达到 corePoolSize 时,新提交任务将被放入 workQueue 中,等待线程池中任务调度执行 ;
-
当 workQueue 已满,且 maximumPoolSize > corePoolSize 时,新提交任务会创建新线程执行任务 ;
-
当提交任务数超过 maximumPoolSize 时,新提交任务由 RejectedExecutionHandler 处理 ;
-
当线程池中超过 corePoolSize 线程,空闲时间达到 keepAliveTime 时,关闭空闲线程 ;
-
当设置 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 的两种用法
-
某一线程在开始运行前等待 n 个线程执行完毕
将 CountDownLatch 的计数器初始化为 n , new CountDownLatch(n) , 每当一个任务线程执行完毕,就将计数器减 1,CountDownLatch.Countdown;当计数器的值变为 0 时,在 CountDownLatch 上 await() 的线程就会被唤醒。
一个典型应用场景就是在启动一个服务时,主线程需要等待多个组件加载完毕;
-
实现多个线程开始执行任务的最大并行性。注意是并行性,而不是并发性,强调的是多个线程在某一时刻同时执行,类似于赛跑,将多个线程放到起点,然后等待发令枪响,最后同时开跑。
做法是初始化一个共享的 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,方便处理更复杂的业务场景。
小结
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为 0 时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为 0 时,无法重置 | 计数达到指定值时,计数置为 0 重新开始 |
调用 countDown() 方法计数减 1,调用 await() 方法只进行阻塞,对计数没有任何影响 | 调用 await() 方法计数加 1,若 加 1 后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可以重复利用 |