摘要描述:【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
一、线程池的原理

1.1 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
1.2. 当调用execute()方法添加一个任务时,任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。 当一个任务通过execute(Runnable)方法欲添加到线程池时,线程池会做如下判断:
a. 如果正在运行的线程数小于corePoolSize,即使线程池中的线程都处于空闲状态,也会创建新的线程运行这个任务。
b. 如果正在运行的线程数大于或者等于corePoolSize,但是缓冲队列 workQueue未满,那么任务将被放入缓存队列。
c. 如果正在运行的线程数大于corePoolSize,缓冲队列workQueue已满,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建新的线程运行这个任务。
d. 如果正在运行的线程数大于corePoolSize,缓冲队列workQueue已满,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。1.3 当一个线程完成任务时,它会从队列中取下一个任务来执行。
1.4 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize时,那么这个线程会被终止,线程池可以动态的调整池中的线程数,当线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
1.5 处理任务的优先级为: 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
二、线程池的使用场景
(1)单个任务处理的时间比较短;
(2)需要处理的任务数量大;
三、线程池的配置参数
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
(1)corePoolSize: 线程池维护线程的最少数量 (core : 核心)
(2)maximumPoolSize: 线程池维护线程的最大数量 ,如果当线程池中的运行的线程数量到达这个数字时,新来的任务会抛出异常。
(3)keepAliveTime: 线程池维护线程所允许的空闲时间 ,表示线程没有任务执行时最多能保持多少时间会停止,然后线程池维护的线程数目维持在corePoolSize。
(4)unit: 线程池维护线程所允许的空闲时间的单位 。
(5)workQueue: 线程池所使用的阻塞队列,用来缓存等待执行的任务,如果当前对线程的需求超过了corePoolSize大小,才会放在这里。
(6)handler: 如果执行线程已满,线程池对拒绝任务的处理策略。线程池的阻塞队列包含哪几种选择?
ArrayBlockingQueue:一个有边界的阻塞队列,它的内部实现是一个数组。它的容量在初始化时就确定不变。
LinkedBlockingQueue:阻塞队列大小的配置是可选的,其内部实现是一个链表。
PriorityBlockingQueue:是一个没有边界的队列,所有插入到PriorityBlockingQueue的对象必须实现java.lang.Comparable接口,队列优先级的排序就是按照我们对这个接口的实现来定义的。
SynchronousQueue:队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。线程池对拒绝任务的处理策略有哪几种?
ThreadPoolExecutor.AbortPolicy():抛出java.util.concurrent.RejectedExecutionException异常 。
ThreadPoolExecutor.CallerRunsPolicy(): 重试添加当前的任务,他会自动重复调用execute()方法 。
ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃旧的任务 。
ThreadPoolExecutor.DiscardPolicy(): 抛弃当前的任务。
四、线程池的使用
package com.layduo.web.test;
import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author layduo
* @createTime 2019年11月19日 上午10:42:39
*
* @resource by https://www.cnblogs.com/sunhaoyu/articles/6955923.html
*/
public class MyThreadPoolExecutor {
private static int produceTaskSleepTime = 1000;
private static int consumeTaskSleepTime = 5000;
private static int produceTaskMaxNumber = 10; // 定义最大添10个线程到线程池中
private static int corePoolSize = 3;
private static int maximumPoolSize = 5;
private static long keepAliveTime = 3;
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= produceTaskMaxNumber; i++) {
try {
// 一个任务,并将其加入到线程池
System.out.println("===================================================");
String work = "task@ " + i;
System.out.println("execute : " + work);
threadPoolExecutor.execute(new ThreadPoolTask(work, threadPoolExecutor));
// 便于观察,等待一段时间
Thread.sleep(produceTaskSleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = true;
while (flag) {
if (threadPoolExecutor.getQueue().size() <= 0 && threadPoolExecutor.getActiveCount() <= 0) {
flag = false;
System.out.println();
System.out.println("====>线程池线程任务执行完毕");
printlnThread(threadPoolExecutor);
// 关闭线程池
threadPoolExecutor.shutdown();
// 检测没有关闭线程池,则强制关闭
if (threadPoolExecutor.isTerminated()) {
threadPoolExecutor.shutdownNow();
System.out.println("========强制关闭线程池========");
}
}
Thread.sleep(3000);
}
}
// 线程池执行的任务
public static class ThreadPoolTask implements Runnable, Serializable {
private static final long serialVersionUID = 0;
// 保存任务所需要的数据
private Object threadPoolTaskData;
private ThreadPoolExecutor threadPool;
public ThreadPoolTask(Object works, ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolTaskData = works;
this.threadPool = threadPoolExecutor;
}
@Override
public void run() {
printlnThread(threadPool);
System.out.println();
System.out.println("run ------> " + threadPoolTaskData + " by " + Thread.currentThread().getName());
System.out.println("===================================================");
try {
// 便于观察,等待一段时间
Thread.sleep(consumeTaskSleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolTaskData = null;
}
public Object getTask() {
return this.threadPoolTaskData;
}
}
public static void printlnThread(ThreadPoolExecutor threadPool) {
System.out.println();
int queueSize = threadPool.getQueue().size();
System.out.println("====>当前排队任务数:" + queueSize);
int activeCount = threadPool.getActiveCount();
System.out.println("====>当前活动任务数:" + activeCount);
long completedTaskCount = threadPool.getCompletedTaskCount();
System.out.println("====>执行完成任务数:" + completedTaskCount);
long taskCount = threadPool.getTaskCount();
System.out.println("====>线程池执行任务数:" + taskCount);
// 通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
int largest = threadPool.getLargestPoolSize();
System.out.println("====>创建最大线程数:" + largest);
System.out.println("====>线程池创建线程是否已满:" + (largest == maximumPoolSize ? "是" : "否"));
long poolSize = threadPool.getPoolSize();
System.out.println("====>总线程数:" + poolSize);
}
}
运行输出控制台:
===================================================
execute : task@ 1
====>当前排队任务数:0
====>当前活动任务数:1
====>执行完成任务数:0
====>线程池执行任务数:1
====>创建最大线程数:1
====>线程池创建线程是否已满:否
====>总线程数:1
run ------> task@ 1 by pool-1-thread-1
===================================================
===================================================
execute : task@ 2
====>当前排队任务数:0
====>当前活动任务数:2
====>执行完成任务数:0
====>线程池执行任务数:2
====>创建最大线程数:2
====>线程池创建线程是否已满:否
====>总线程数:2
run ------> task@ 2 by pool-1-thread-2
===================================================
===================================================
execute : task@ 3
====>当前排队任务数:0
====>当前活动任务数:3
====>执行完成任务数:0
====>线程池执行任务数:3
====>创建最大线程数:3
====>线程池创建线程是否已满:否
====>总线程数:3
run ------> task@ 3 by pool-1-thread-3
===================================================
===================================================
execute : task@ 4
===================================================
execute : task@ 5
====>当前排队任务数:1
====>当前活动任务数:3
====>执行完成任务数:1
====>线程池执行任务数:5
====>创建最大线程数:3
====>线程池创建线程是否已满:否
====>总线程数:3
run ------> task@ 4 by pool-1-thread-1
===================================================
===================================================
execute : task@ 6
====>当前排队任务数:1
====>当前活动任务数:3
====>执行完成任务数:2
====>线程池执行任务数:6
====>创建最大线程数:3
====>线程池创建线程是否已满:否
====>总线程数:3
run ------> task@ 5 by pool-1-thread-2
===================================================
===================================================
execute : task@ 7
====>当前排队任务数:1
====>当前活动任务数:3
====>执行完成任务数:3
====>线程池执行任务数:7
====>创建最大线程数:3
====>线程池创建线程是否已满:否
====>总线程数:3
run ------> task@ 6 by pool-1-thread-3
===================================================
===================================================
execute : task@ 8
===================================================
execute : task@ 9
===================================================
execute : task@ 10
====>当前排队任务数:3
====>当前活动任务数:4
====>执行完成任务数:3
====>线程池执行任务数:10
====>创建最大线程数:4
====>线程池创建线程是否已满:否
====>总线程数:4
run ------> task@ 10 by pool-1-thread-4
===================================================
====>当前排队任务数:2
====>当前活动任务数:4
====>执行完成任务数:4
====>线程池执行任务数:10
====>创建最大线程数:4
====>线程池创建线程是否已满:否
====>总线程数:4
run ------> task@ 7 by pool-1-thread-1
===================================================
====>当前排队任务数:1
====>当前活动任务数:4
====>执行完成任务数:5
====>线程池执行任务数:10
====>创建最大线程数:4
====>线程池创建线程是否已满:否
====>总线程数:4
run ------> task@ 8 by pool-1-thread-2
===================================================
====>当前排队任务数:0
====>当前活动任务数:4
====>执行完成任务数:6
====>线程池执行任务数:10
====>创建最大线程数:4
====>线程池创建线程是否已满:否
====>总线程数:4
run ------> task@ 9 by pool-1-thread-3
===================================================
====>线程池线程任务执行完毕
====>当前排队任务数:0
====>当前活动任务数:0
====>执行完成任务数:10
====>线程池执行任务数:10
====>创建最大线程数:4
====>线程池创建线程是否已满:否
====>总线程数:3
本文详细介绍了Java线程池的工作原理、使用场景、配置参数,以及四种拒绝任务处理策略。强调了不应使用Executors创建线程池,而应使用ThreadPoolExecutor以更好地控制线程池的行为,避免资源耗尽风险。
826

被折叠的 条评论
为什么被折叠?



