池化设计思想
预设初始资源,不用等用到的时候再创建。
举例:食堂大妈预先打几份菜放着,拿了就能走,不用临时打菜。
应用:线程池、各种连接池。
Java线程池工作流程:
创建线程池的几个核心构造参数
// Java线程池的完整构造函数
public ThreadPoolExecutor(
int corePoolSize, // 线程池的核心线程数,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务
int maximumPoolSize, // 最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize
long keepAliveTime, // 线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //任务队列。新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务
ThreadFactory threadFactory, // 线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是后台线程等。
RejectedExecutionHandler handler // 拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用
)
Java线程池类型
//创建固定核心数的线程池,这里核心数 = 3
ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建单核心的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建一个按照计划规定执行的线程池,这里核心数 = 2
ExecutorService executorService = Executors.newScheduledThreadPool(2);
//创建一个可缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
FixedThreadPool 定长线程池
核心线程数和最大线程数相等,keepAliveTime为0(即不生效,不会回收线程)。任务队列为链表结构的无界队列。
SingleThreadExecutor 单线程化线程池
定长线程池线程数为1的情况。
ScheduledThreadPool 定时线程池
核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
CachedThreadPool 可缓存线程池
无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
注意:
- FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存(来任务就进队等待,无界限),甚至 OOM。
- CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
线程池类型及对应队列
线程池 | 实现队列 |
---|---|
newFixedThreadPool | LinkedBlockingQueue |
newSingleThreadExecutor | LinkedBlockingQueue |
newScheduledThreadPool | DelayQueue |
newCachedThreadPool | SynchronousQueue |
队列特点:
LinkedBlockingQueue:基于链表,先进先出,线程安全。
DelayQueue:延时获取元素,无界限(基于二叉树),线程安全。
SynchronousQueue:只存储单个元素(放入元素时要等相应的消费者取走元素),支持先进先出后进先出。
线程池拒绝策略
线程数达到最大值且任务队列已满时,会执行拒绝策略。(任务提交数>maxPoolSize + queueCapacity)
JDK内置四种拒绝策略
AbortPolicy(ThreadPoolExecutor默认):
- 直接丢弃任务,并抛出RejectedExecutionException异常
CallerRunsPolicy:
- 该任务被线程池拒绝,由调用execute方法的线程执行该任务
DiscardPolicy:
- 直接丢弃任务,什么都不做
DiscardOldestPolicy:
- 抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
注意:
- ExecutorService中的线程池实例队列都是无界的,即视OOM也不会出发拒绝策略。
自定义拒绝策略:
- 通过实现RejectedExecutionHandler接口,自定义一个拒绝策略类,重写它的rejectedExecution()方法:
线程池Demo
public void testThread() {
List<String> test = new ArrayList<>();
int i = 0;
while (i < 10001) {
i++;
test.add(i + "");
}
//给List加锁,不加会丢数据
List<String> thread = Collections.synchronizedList(test);
//创建线程池
ExecutorService exec = Executors.newFixedThreadPool(3);
thread.forEach(u -> {
exec.execute(() -> {
System.out.println("线程:"+Thread.currentThread().getName());
System.out.println("thread:"+u);
});
});
//任务结束关闭线程池
exec.shutdown();
//判断线程池是否结束,不加会直接结束方法
while (true) {
if (exec.isTerminated()) {
break;
}
}
}