实际生产中,我们需要手写一个线程池来减少线程的创建释放。
常见的创建方式:
newFixedThreadPool | 该方法返回一个固定线程数量的线程池 |
---|---|
newSingleThreadExecutor | 该方法返回一个只有一个现成的线程池 |
newCachedThreadPool | 返回一个可以根据实际情况调整线程数量的线程池 |
newSingleThreadScheduledExecutor | 该方法和newSingleThreadExecutor的区别是给定了时间执行某任务的功能,可以进行定时执行等; |
newScheduledThreadPool | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行,可以指定线程数量 |
创建方式Executors.xxx
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0; i < 10; i++){
int index = i;
executorService.submit(() -> System.out.println("i:" + index + " executorService"));
}
executorService.shutdown();
}
}
问题:
submit(Runnable task)方法提交一个线程
* 1)newFixedThreadPool和newSingleThreadExecutor:
* 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
* 2)newCachedThreadPool和newScheduledThreadPool:
* 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
解决方案:
使用ThreadPoolExecutor
构建线程池。
public class ThreadPoolDemo2 {
/**
* corePoolSize 核心线程池大小
* maximumPoolSize 线程池最大容量大小;
* keepAliveTime线程池空闲时,线程存活的时间;
* TimeUnit 时间单位;
* workQueue 任务队列;
* handler线程拒绝策略;
* @param args
*/
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(20, 40, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
int index = i;
executorService.submit(() -> {
System.out.println("i:" + index + " executorService");
try {
Thread.sleep(20000);
} catch (Exception e) {
e.printStackTrace();
}
}
);
}
executorService.shutdown();
}
}
自定义ThreadFactory、自定义线程拒绝策略。
public class ThreadPoolDemo3 {
/**
* corePoolSize 核心线程池大小
* maximumPoolSize 线程池最大容量大小;
* keepAliveTime线程池空闲时,线程存活的时间;
* TimeUnit 时间单位;
* workQueue 任务队列;
* handler线程拒绝策略;
*/
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10), new ThreadFactory() { // 自定义ThreadFactory
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName(r.getClass().getName());
return thread;
}
}, new ThreadPoolExecutor.AbortPolicy());// 自定义线程拒绝策略
for (int i = 0; i < 10; i++) {
int index = i;
/**
* 发现没报异常,少了0;或者使用get()接收。阻塞当前线程直到任务完成。
* 而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
*/
Future<?> future = executorService.submit(() -> System.out.println(1/index + " index:" + index));
try {
future.get();
} catch (Exception e) {
e.printStackTrace();
}
//使用execute代替。
// executorService.execute(() -> System.out.println(1/index + " index:" + index));
}
executorService.shutdown();
}
}
坑点:
- submit的坑:
使用submit提交task的时候,当里面某条线程有异常的情况下,没有异常抛出。
解决方案:使用execute
代替。或者使用Future
接受返回结果,再get
异常出来。
参数意义
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE, //核心线程数
MAXIMUM_POOL_SIZE, //线程池所容纳最大线程数(workQueue队列满了之后才开启)
KEEP_ALIVE, //非核心线程闲置时间超时时长 unit:keepAliveTime的单位
TimeUnit.SECONDS, //时间单位
new ArrayBlockingQueue<Runnable>(40), //等待队列,存储还未执行的任务
Executors.defaultThreadFactory(), //线程默认工厂
new ThreadPoolExecutor.AbortPolicy()); //线程拒绝策略
运行过程:
1. 线程池刚创建时,里面没有一个线程。
2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a、如果正在运行的线程数量小于 corePoolSize
,那么马上创建线程运行这个任务;
b、如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列
。
c、如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize
,那么还是要创建线程运行这个任务;
d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常
,告诉调用者“我不能再接受任务了”。
3 、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4 、当一个线程无事可做,超过一定的时间(keepAliveTime
)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
经过jmeter压力测试亲测:最大线程总数为maximumPoolSize+队列数目。
拒绝策略:
当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。
拒绝策略 | 含义 |
---|---|
ThreadPoolExecutor.AbortPolicy(默认) | 处理程序遭到拒绝将抛出运行时 RejectedExecutionException |
ThreadPoolExecutor.CallerRunsPolicy | 线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度 |
ThreadPoolExecutor.DiscardPolicy | 不能执行的任务将被删除。 |
ThreadPoolExecutor.DiscardOldestPolicy | 如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。 |
综合考虑:使用CallerRunsPolicy
起码本线程会执行,好过直接丢弃。
附录:
生产环境使用的线程池工具类。
public class ThreadPool {
/**
* 说明:下面这些常量我是根据AsyncTask的源码配置的,大家可以根据自己需求自行配置
*/
// 根据cpu的数量动态的配置核心线程数和最大线程数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// 核心线程数 = CPU核心数 + 1
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
// 线程池最大线程数 = CPU核心数 * 2 + 1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
// 非核心线程闲置时超时1s
private static final int KEEP_ALIVE = 1;
// 要确保该类只有一个实例对象,避免产生过多对象消费资源,所以采用单例模式
private ThreadPool() {
System.out.println("------------------核心线程数 = CPU核心数 + 1" + CORE_POOL_SIZE +"--------------");
}
private static ThreadPool sInstance;
public synchronized static ThreadPool getsInstance() {
if (sInstance == null) {
sInstance = new ThreadPool();
}
return sInstance;
}
// 线程池的对象
private ThreadPoolExecutor executor;
// 使用线程池,线程池中线程的创建完全是由线程池自己来维护的,我们不需要创建任何的线程
// 我们所需要做的事情就是往这个池子里面丢一个又一个的任务
public void execute(Runnable r) {
if (executor == null) {
/**
* corePoolSize:核心线程数
* maximumPoolSize:线程池所容纳最大线程数(workQueue队列满了之后才开启)
* keepAliveTime:非核心线程闲置时间超时时长 unit:keepAliveTime的单位
* workQueue:等待队列,存储还未执行的任务 threadFactory:线程创建的工厂 handler:异常处理机制
*
*/
executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(20), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
executor.execute(r);// 把一个任务丢到了线程池中
}
public void cancel(Runnable r) {
if (r != null) {
executor.getQueue().remove(r);// 把任务移除等待队列
}
}