什么是线程池?为什么要使用线程池?
1、降低资源的消耗,降低线程创建和销毁的资源消耗
2、提高响应速度,避免创建销毁消耗时间;
3、提高线程的可管理性
实现一个我们自己的线程池
1、线程必须在池子已经创建好,并且可以保持住,要有容器保存多个线程
2、线程还能接受外部的任务并且运行;
JDK中的线程池和工作原理
ThreadPoolExecutor ,jdk所有线程池实现的父类
各个参数的含义
int corePoolSize, 核心线程数, < corePoolSize ,就会创建新线程 = corePoolSize , 保存到 BlockingQueue (int prestartAllCoreThreads() 调用这个方法 ,就会一次性创建 corePoolSize 的 线程 )
int maximumPoolSize, 允许的最大线程数,BlockingQueue 满了, 但是小于 maxmumPoolSize 的时候 就会创建新的线程
long keepAliveTime, 存活时间,在 线程数 > corePoolSize 的时候,空闲时间超过 keepAliveTime 就会销毁
TimeUnit unit, 存活时间的单位
BlockingQueue workQueue, 保存任务的阻塞队列
ThreadFactory threadFactory, 线程工厂,给新建的线程赋名字
RejectedExecutionHandler handler 饱和策略
AbortPolicy 直接抛出异常,默认
CallerRunsPolicy 用调用者所在的线程来执行任务;谁提交的任务谁执行
DiscardOldestPolicy 丢弃阻塞队列里最老的任务,队列里最靠前的任务
DiscardPolicy 丢弃当前任务
提交任务
void execute(Runnable command) 无返回
Future submit(Runnable task, T result) 需要返回
工作机制
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/ *
*分3步进行:
*
* 1.如果少于正在运行的corePoolSize线程,尝试
*以给定命令作为第一个线程来启动新线程
*任务。 对addWorker的调用自动检查runState和
* workerCount,因此可以防止增加误报
*通过返回false而不应该进行线程化。
*
* 2.如果一个任务可以成功排队,那么我们仍然需要
*仔细检查我们是否应该添加线程
*(因为现有的自上次检查后死亡)或
*自从进入此方法以来,该池已关闭。 所以我们
*重新检查状态,并在必要时回退排队
*停止,如果没有,则启动一个新线程。
*
* 3.如果我们无法将任务排队,那么我们尝试添加一个新的
*线程。 如果失败,我们知道我们已经关闭或饱和
*并因此拒绝任务。
* /
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
关闭线程池
void shutdown();设置线程池的状态,只会中断所有没有执行任务的线程
List shutdownNow(); 设置线程池的状态,还会去尝试停止正在运行或者暂停任务的线程;
java里的线程 协作式的,不是抢占式,调用了 shutdownNow 也不一定马山停止
合理配置线程池
根据任务的性质:计算密集型(CPU密集型)、IO密集型、混合型;
计算密集型 : 加密,大数分解,正则
线程数适当小一点、最大推荐:CPU核心数+1;
为什么+1 ?
防止 页缺失(需要执行的数据应该全部加载都内存中,但是因为一些原因某些数据还在磁盘上,操作系统需要把这些数据调度到内存中,这种情况叫做页缺失)
为什么只能+1?
因为计算密集型必须要把CPU充分利用起来,线程多了,就会产生上下文切换的消耗
Runtime.getRuntime().availableProcessors() 这个方法可以获取当前机器的核心数;
IO密集型 : 读取文件、数据库连接、 网络通信、rpc;
线程数适当大一点 、推荐:机器CPU的核心数*2;
上线以后根据业务实际情况调整 (用户态、系统态)
混合型 : 既有 计算密集型 又有 IO密集型
尽量拆分,
IO密集型 >> 计算密集型, 拆分意义不大
队列的选择上 : 应该使用有界(拒绝可以重新提交执行),无界队列可能会导致内存溢出 OOM
系统为我们预定义的线程池详解
FixedThreadPool
corePoolSize == maximumPoolSize
创建固定线程数量的,适用于 负载较重的服务器,使用了无界队列
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
SingleThreadExecutor
创建单个线程,需要保证顺序执行任务,不会有多个线程启动,使用了无界队列;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
CachedThreadPool
会根据需要来创建新线程,执行很多短期异步任务的程序,使用了 SynchronousQueue, 任务量新增速度大于任务处理速度时,很容易崩
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
SingleThreadScheduledExecutor
单线程执行周期任务
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
ScheduledThreadPool
相同作用:Timer(问题多,抛出异常整个停止)
适用于定期执行任务, 可控制后台线程数量
为了保证程序健壮性,可将异常捕捉;如不捕获,抛出异常,任务将停止;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
建议提交给 ScheduledThreadPool 执行的任务要 catch 异常;免得任务停止;
方法
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) ; 只执行一次,任务还可以延时执行;
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) ;提交固定时间间隔的任务(开始间隔时间相等)
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) ;提交固定延迟时间间隔执行的任务
(结束时间和下次开始时间间隔相等)
scheduleAtFixedRate 任务超时:
60s执行一次,有任务执行了80s,下一个任务马上开始执行;
第一个任务80s,第二个任务20s,第三个任务50s;
第一个任务 0s 开始 ,80s结束,第二个任务 80s 开始,100s 结束; 第三个任务 120s 开始;第四个任务 180s 开始;
Executor框架
基本使用流程
实际开发中,通常会 new ThreadPoolExecutor(), 因为预定义的线程池都使用了无界队列,
拒绝策略,通常需自定义;
CompletionService
先完成的任务先返回
//完成的任务才会被放进去 BlockingQueue
public class ExecutorCompletionService<V> implements CompletionService<V> {
private final Executor executor;
private final AbstractExecutorService aes;
//完成的任务才会被放进去 BlockingQueue
private final BlockingQueue<Future<V>> completionQueue;
/**
* FutureTask extension to enqueue upon completion
*/
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
private RunnableFuture<V> newTaskFor(Callable<V> task) {
if (aes == null)
return new FutureTask<V>(task);
else
return aes.newTaskFor(task);
}
private RunnableFuture<V> newTaskFor(Runnable task, V result) {
if (aes == null)
return new FutureTask<V>(task, result);
else
return aes.newTaskFor(task, result);
}
}