一、前言
我们知道了只个并发容器之后,那么看我们线程池 也是基于并发容器实现的
为什么要有线程池:
1、避免线程频繁的创建和销毁,浪费资源
2、提高响应速度,需要获取线程的直接去线程池中获取,不用创建,也节约了需要销毁线程时间
3、便于统一管理:线程可以放到线程池里面统一管理
二、实现线程池的几个要点
1、需要提前创建好,并可以保持
2、可以向线程池提交任务,并有执行任务的能务
3、可以接收来不及运行的任务
4、可以销毁
三、 jdk中线程池和工作机制
1、线程创建
ThreadPoolExecutor,jdk所有线程池实现的父类
2、ThreadPoolExecutor构造参数
coresize:线程池中核心线程数,小于coreSize就会创建新线程,等于coreSize就是将线程放到阻塞队列中,只有调用prestartAllCoreThreads()方法,才会一次性启动coreSize个线程
maxPoolSize:允许的最大线程数,阻塞队列也满了,< maxPollSize时,会创建新线程执行
keepAliveTime:线程空闲下来后,存活的时间,只有线程池线程个数》 coresize才有用
TimeUnit unit, 存活时间的单位值
BlockingQueue workQueue, 保存任务的阻塞队列
ThreadFactory threadFactory, 创建线程的工厂,给新建的线程赋予名字
RejectedExecutionHandler handler :饱和策略,有如下几种:
AbortPolicy :直接抛出异常,默认;
CallerRunsPolicy:用调用者所在的线程来执行任务
DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务
DiscardPolicy :当前任务直接丢弃
实现自己的饱和策略,实现RejectedExecutionHandler接口即可
实际应用中,如果线程过多,不可能将任务丢弃,那么我们需要实现自己的饱和策略,如果写入数据库,后面再通过定时 将丢弃的任务重新执行,或者推送到消息中间件 去消费,或打日志等。
3、提交任务
execute(Runnable command) 不需要返回
Future submit(Callable task) 需要返回
4、关闭线程池
shutdown(),shutdownNow();
shutdownNow():设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程
shutdown()设置线程池的状态,只会中断所有没有执行任务的线程
5、工作机制
线程池在初始化的并不会创建线程池,在提交任务的时候才会创建线程
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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))//加入队列失败《 maxsize,进行新线程执行
reject(command); //拒绝新任务
}
所以线程执行顺序
3、常用线程池
FixedThreadPool
创建固定线程数量的,适用于负载较重的服务器,使用了无界队列
SingleThreadExecutor
创建单个线程,需要顺序保证执行任务,不会有多个线程活动,使用了无界队列
CachedThreadPool
会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueue
WorkStealingPool(JDK7以后)
基于ForkJoinPool实现
ScheduledThreadPoolExecutor
需要定期执行周期任务,Timer不建议使用了。
newSingleThreadScheduledExecutor:只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务
newScheduledThreadPool 可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候
方法说明:
schedule:只执行一次,任务还可以延时执行
scheduleAtFixedRate:提交固定时间间隔的任务
scheduleWithFixedDelay:提交固定延时间隔执行的任务
两者的区别:
scheduleAtFixedRate任务超时:
规定60s执行一次,有任务执行了80S,下个任务马上开始执行
第一个任务 时长 80s,第二个任务20s,第三个任务 50s
第一个任务第0秒开始,第80S结束;
第二个任务第80s开始,在第100秒结束;
第三个任务第120s秒开始,170秒结束
第四个任务从180s开始
参加代码:ScheduleWorkerTime类,执行效果如图
4、Executor框架
了解CompletionService
执行线程的任务有多个,需要得到结果,那么这个结果需要存下来,需要取的时候能取到
将结果放入到容器中存储,1先放 2 先放
自己实现:只能也是只能按顺序拿结果 只能先拿1 再拿2 这有问题
通过CompletionService
去线程池中拿任务
先完成的任务要,可以通过CompletionService先拿到