线程池的体系结构:
java.util.concurrent.Executor 负责线程的使用和调度的根接口
|–ExecutorService 子接口: 线程池的主要接口
|–ThreadPoolExecutor 线程池的实现类
|–ScheduledExceutorService 子接口: 负责线程的调度
|–ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现了ScheduledExecutorService
工具类 : Executors
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。 线程池中只有一个线程
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务
ThreadPoolExcutor
线程池的优势:
降低资源消耗、提高响应速度、提高线程的可管理性
线程池的创建:
new ThreadPoolExecutor(
int corePoolSize, //线程池维护线程的最少数量 (core : 核心)
int maximumPoolSize, //线程池维护线程的最大数量
long keepAliveTime, //线程池维护线程所允许的空闲时间
TimeUnit unit, // 线程池维护线程所允许的空闲时间的单位
BlockingQueue<Runnable> workQueue, //线程池所使用的缓冲队列
ThreadFactory threadFactory, //线程池工厂,用来创建线程,可定制一个ThreadFactory来达到统一命名我们线程池中的线程的目的
RejectedExecutionHandler handler //线程池对拒绝任务的处理策略
)
线程池的拒绝策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池的工作顺序:
corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略
ThreadPoolExecutor执行过程:
1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3.当线程数大于等于核心线程数,且任务队列已满。(1)若线程数小于最大线程数,创建线程。(2)若线程数等于最大线程数,抛出异常,拒绝任务。
Java 线程池
Java通过Executors提供四种线程池,分别为:
newFixedThreadPool
示例代码如下:
这种线程池里面的线程被设计成存放固定数量的线程,具体线程数可以考虑为CPU核数*N(N可大可小,取决于并发的线程数,计算机可用的硬件资源等)。可以通过下面的代码来获取当前计算机的CPU的核数。
int processors = Runtime.getRuntime().availableProcessors();
-
newFixedThreadPool复用固定数量的线程处理一个共享的无边界队列 。任何时间点,最多有 nThreads 个线程会处于活动状态执行任务。如果当所有线程都是活动时,有多的任务被提交过来,那么它会一致在队列中等待直到有线程可用。如果任何线程在执行过程中因为错误而中止,新的线程会替代它的位置来执行后续的任务。所有线程都会一致存于线程池中,直到显式的执行 ExecutorService.shutdown() 关闭。
-
由于阻塞队列使用了LinkedBlockingQueue,是一个无界队列,因此永远不可能拒绝任务。LinkedBlockingQueue在入队列和出队列时使用的是不同的Lock,意味着他们之间不存在互斥关系,在多CPU情况下,他们能正在在同一时刻既消费,又生产,真正做到并行。因此这种线程池不会拒绝任务,而且不会开辟新的线程,也不会因为线程的长时间不使用而销毁线程。这是典型的生产者----消费者问题,这种线程池适合用在稳定且固定的并发场景,比如服务器。
newSingleThreadExecutor
- SingleThreadExecutor是使用单个worker线程的Executor,作为单一worker线程的线程池,SingleThreadExecutor把corePoolSize和maximumPoolSize均被设置为1,和FixedThreadPool一样使用的是无界队列LinkedBlockingQueue,所以带来的影响和FixedThreadPool一样。
- 对于newSingleThreadExecutor()来说,也是当线程运行时抛出异常的时候会有新的线程加入线程池替他完成接下来的任务。创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- 所以这个比较适合那些需要按序执行任务的场景。比如:一些不太重要的收尾,日志等工作可以放到单线程的线程中去执行。日志记录一般情况会比较慢(数据量大一般可能不写入数据库),顺序执行会拖慢整个接口,堆积更多请求,还可能会对数据库造成影响(事务在开启中),所以日志记录完全可以扔到单线程的线程中去,一条条的处理,也可以认为是一个单消费者的生产者消费者模式。
newCachedThreadPool
CachedThreadPool就像是一堆人去一个很大的咖啡馆喝咖啡,里面服务员也很多,随时去,随时都可以喝到咖啡。但是为了响应国家的“光盘行动”,一个人喝剩下的咖啡会被保留60秒,供新来的客人使用。如果你运气好,没有剩下的咖啡,你会得到一杯新咖啡。但是以前客人剩下的咖啡超过60秒,就变质了,会被服务员回收掉。
- 核心池大小为0,线程池最大线程数目为最大整型,这意味着所有的任务一提交就会加入到阻塞队列中。当线程池中的线程60s没有执行任务就终止;
- 阻塞队列为SynchronousQueue。SynchronousQueue的take操作需要put操作等待,put操作需要take操作等待,否则会阻塞(线程池的阻塞队列不能存储,所以当目前线程处理忙碌状态时,所以开辟新的线程来处理请求),线程进入wait set。
总结下来:
①这是一个可以无限扩大的线程池;
②适合处理执行时间比较小的任务;
③线程空闲时间超过60s就会被杀死,所以长时间处于空闲状态的时候,这种线程池几乎不占用资源;
④阻塞队列没有存储空间,只要请求到来,就必须找到一条空闲线程去处理这个请求,找不到则在线程池新开辟一条线程。如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
newScheduledThreadPool
相比于第一个固定个数的线程池强大在 ①可以执行延时任务,②也可以执行带有返回值的任务
核心线程数量固定、非核心线程数量无限制(闲置时马上回收)、 唯一一个具有延迟执行和周期重复执行任务功能的线程池
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
各个线程池的区别和使用场景:
newFixedThreadPool:
只有核心线程且不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待)
场景:执行长期的任务,性能好很多
newSingleThreadExecutor:
只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行)
场景:一个任务一个任务执行的场景
newCachedThreadPool:
只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时)、任何线程任务到来都会立刻执行,不需要等待
场景:执行很多短期异步的小程序或者负载较轻的服务器
NewScheduledThreadPool:
创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
场景:周期性执行任务的场景