线程池

为什么需要线程池

问题一:反复创建线程开销大
问题二:过多的线程会占用太多的内存

解决思路:
用少量的线程-避免内存占用过多
让这部分线程都保持工作,且可以反复的执行任务-避免生命周期的损耗

线程池好处

加快响应速度
合理利用CPU和内存
统一管理

线程池适合应用的场景

服务器接收大量请求时,使用线程池技术是非常合适的
在开发中,如果需要创建5个以上的线程,就可以使用线程池来管理

增减线程的时机

创建和停止线程池

线程池构造函数的参数

截屏2020-02-03下午4.45.24.png


corePoolSize:核心线程数,一直存在
maxPoolSize:最大线程数,当workQueue装满之后会创建不超过maxPoolSize的新的线程
keepAliveTime:超过corePoolSize数量的线程,空闲keepAliveTIme后将被回收
workQueue:任务存储队列
3种常见的队列类型:
1)直接交接:SynchronousQueue(没有容量的队列,作用是直接创建新线程)
2)无界队列:LinkedBlockingQueue
3)有界队列:ArrayBlockingQueue
threadFactory:创建线程的工厂,自己指定可以改变线程名、线程组、优先级、守护线程等
handler:线程池无法接受提交的任务,将使用的拒绝策略

添加线程规则

截屏2020-02-03下午4.51.03.png

截屏2020-02-03下午5.02.44.png

增减线程的特点

  1. 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
  2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
  3. 通过设置maximumPoolSize为很高的值,例如:Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
  4. 只有在队列填满时才会创建多于corePoolSize的线程,所以如果你使用的是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSIze


线程池应该手动创建还是自动创建

Executors

newFixedThreadTool

  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. }

固定大小的线程池,由于workQueue使用的是LinkedBlockingQueue没有容量上限的,当请求多到无法及时处理时,将存储到队列中,会容易占用大量内存,有OOM的可能性

newSingleThreadExecutor

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }

一个线程的线程池,和newFixedThreadTool原理一样,只是将corePoolSize设置为了1

newCachedThreadPool

  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }

无限制创建线程,任务结束将自动回收

newScheduledThreadPool

  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  2. return new ScheduledThreadPoolExecutor(corePoolSize);
  3. }

创建一个线程池,该线程池可以调度命令在给定延迟后运行,或定期执行
延迟执行
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,删除等待队列

拒绝任务

拒绝时机

  1. 当Executor,提交新任务会被拒绝
  2. 缓存队列和最大线程数都已饱和

4中拒绝策略

AbortPolicy
直接抛异常
DiscardPolicy
丢弃新提交的任务
DiscardOldestPolicy
丢弃老的任务
CallerRunsPolicy
在当前线程运行任务

钩子方法,给线程池加点料

每个任务执行前后
日志、统计

线程池原理

Executor家族

截屏2020-02-03下午8.42.13.png

上面是线程池的继承关系图
Executors为创建线程池的工具类

线程池实现线程复用原理

相同线程执行不同任务
在线程中的run方法中,使用循环获取Runable 并调用它的run方法

线程池状态及使用注意点

线程池状态

  • RUNNING:接收新任务并处理排队任务
  • SHUTDOWN:不接收新任务,但处理排队任务
  • STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务
  • TIDYING:中文意思是整洁,所有任务都已终止,workerCount为零时,线程会转换到TIDYING状态,并将运行terminate()钩子方法
  • TERMINGTED:terminate()运行完成

    使用线程池注意点

  • 避免任务堆积

  • 避免线程数过度增加
  • 排查线程泄漏