线程池
为什么需要线程池
问题一:反复创建线程开销大
问题二:过多的线程会占用太多的内存
解决思路:
用少量的线程-避免内存占用过多
让这部分线程都保持工作,且可以反复的执行任务-避免生命周期的损耗
线程池好处
加快响应速度
合理利用CPU和内存
统一管理
线程池适合应用的场景
服务器接收大量请求时,使用线程池技术是非常合适的
在开发中,如果需要创建5个以上的线程,就可以使用线程池来管理
增减线程的时机
创建和停止线程池
线程池构造函数的参数
corePoolSize:核心线程数,一直存在
maxPoolSize:最大线程数,当workQueue装满之后会创建不超过maxPoolSize的新的线程
keepAliveTime:超过corePoolSize数量的线程,空闲keepAliveTIme后将被回收
workQueue:任务存储队列
3种常见的队列类型:
1)直接交接:SynchronousQueue(没有容量的队列,作用是直接创建新线程)
2)无界队列:LinkedBlockingQueue
3)有界队列:ArrayBlockingQueue
threadFactory:创建线程的工厂,自己指定可以改变线程名、线程组、优先级、守护线程等
handler:线程池无法接受提交的任务,将使用的拒绝策略
添加线程规则
增减线程的特点
- 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
- 通过设置maximumPoolSize为很高的值,例如:Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
- 只有在队列填满时才会创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSIze
线程池应该手动创建还是自动创建
Executors
newFixedThreadTool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
固定大小的线程池,由于workQueue使用的是LinkedBlockingQueue没有容量上限的,当请求多到无法及时处理时,将存储到队列中,会容易占用大量内存,有OOM的可能性
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
一个线程的线程池,和newFixedThreadTool原理一样,只是将corePoolSize设置为了1
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
无限制创建线程,任务结束将自动回收
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
创建一个线程池,该线程池可以调度命令在给定延迟后运行,或定期执行
延迟执行
schedule(Runnable command,long delay, TimeUnit unit)
周期任务
scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
正确创建线程池的方法
根据不同的业务场景,自己设置线程池参数,比如我们的内存有多大,我们想给线程取什么名字
线程池里的线程数量设为多少比较合适?
- CPU密集型(加密、计算hash等):最佳线程数为cpu核心数的1-2倍左右
- 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍,以JVM线程监控显示繁忙为依据,保证线程空闲可以衔接上,参考Brain Goetz推荐的计算方法
- 线程数=CPU核心数*(1+平均等待时间/平均工作时间)
停止线程池的正确方法
shutdown
不再接收新的任务,会把已经提交的任务执行完毕
isShutdown
是否调用了shutdown
isTerminated
线程池是否关闭
awaitTermination
几秒钟后是否关闭
shutdownNow
立即关闭,运行线程调用interrupt,删除等待队列
拒绝任务
拒绝时机
- 当Executor,提交新任务会被拒绝
- 缓存队列和最大线程数都已饱和
4中拒绝策略
AbortPolicy
直接抛异常
DiscardPolicy
丢弃新提交的任务
DiscardOldestPolicy
丢弃老的任务
CallerRunsPolicy
在当前线程运行任务
钩子方法,给线程池加点料
每个任务执行前后
日志、统计
线程池原理
Executor家族
上面是线程池的继承关系图
Executors为创建线程池的工具类
线程池实现线程复用原理
相同线程执行不同任务
在线程中的run方法中,使用循环获取Runable 并调用它的run方法