线程池:避免了创建线程和销毁线程的资源损耗。
Executors提供四种线程池:
newCachedThreadPool :缓存线程池,如果线程池长度超过处理需要,可回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool : 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool : 计划线程池,支持定时及周期性任务执行。
newSingleThreadExecutor :单线程线程池,用唯一的线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
先了解下线程池必备的以下几个属性:
poolSize : 当前程序运行的线程数。
coreSize: 核心线程数。
MaximumPoolSize: 线程池中最大线程数。
keepAliveTime:线程空闲时间超过,则会超时退出。
BlockingQueue :设置一个阻塞用来存放将要执行的等待任务。
(线程安全:保证取队列元素时一个只能取一个,且头元素没有时不能取,满时不能加入)
线程池的运行流程:
当需要执行一个任务时:
poolsize < coresize ->开启一个新的线程执行
poolsize = coresize && 队列未满 -> 这时已经达到了核心线程数,线程不够了,加入到阻塞队列中,慢慢执行。
Poolsize < MaximumPoolSize && 队列已满 -> 这时队列都满了,那不行啊 ,快爆了,在开启一个新线程吧。
(通常超出核心线程的线程是“借”的,也就是说超出核心线程的情况算是一种能够预见的异常情况,并且这种情况并不常常发生(如果常常发生,那我想你应该调整你的核心线程数了)
Poolsize = MaximumPoolSize && 队列满了 -> 已经所有办法用尽了,会根据饱和策略RejectedExecutionHandler拒绝新的任务。
(如果设置了keepAliveTime,可以通过以下配置来确定小于corePoolSize时,是否启动超时处理。
但是对于超出MaximumPoolSize,一定会进行超时处理,因为这些线程本就不是正常情况。
allowCoreThreadTimeOut:是否允许核心线程超时退出。
如果该值为false(默认),且poolSize<=corePoolSize,线程池都会保证这些核心线程处于存活状态,不会超时退出。(所以只要不是超过corePoolSize,线程都会空闲而不是销毁)
如果为true,则不论poolSize的大小,都允许超时退出。
如果poolSize>corePoolSize,则该参数不论true还是false,都允许超时退出。
)
(所以大概流程:执行一个任务,如果线程少于coresize则启动新线程,如果等于了coresize,则加入队列等待。如果队列满了,实再不行了,则在开启新线程。如果线程等于最大线程了,执行饱和策略拒绝任务)
自定义线程:可以发现,其实就是我们说的那几个参数,特别简单。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
/**
*coresieze和maximumPool相同,所以这也是为什么他是固定大小。
并且阻塞LinkedBlockingQueue没有初始化大小,是个无界队列。所以进来的任务,线程不够就会都加入队列等待。
keepAliveTime:0,默认超过最大线程才会启用超时,意味着:一超过最大线程数,立即关闭。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* coresieze和maximumPool都为1
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/**
* 核心线程为0,最大线程数无限大。意味着,所有线程都有keepalivetime,都有一小段的生命周期。(缓存线程也就是说,执行的线程会缓存一段时间,然后到时间释放)在使用这类无限大的线程池时,非常容易内存消耗殆尽。这也是为什么阿里不建议使用Executors创建线程池。其实自定义线程池非常好用,而且自己看了就会非常明确。
SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作(即每个任务都必须有线程接管,否则不加入),否则插入操作一直处于阻塞状态
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
*定时线程使用的是ThreadPoolExecutor子类
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
线程池拒绝策略:
当线程池满了,队列也满了,这时候需要执行拒绝策略。默认策略感觉不太好,如果一满就会报异常停止运行。
AbortPolicy(默认)
默认策略,直接跑出异常阻止系统正常运行
CallerRunsPolicy
“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回馈至调用方,比如main线程
DiscardOldestPolicy
抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
DiscardPolicy
直接丢弃任务,不给予任何处理也不跑出异常,如果允许任务丢失