Java提供了丰富的线程池,但是通常我们需要根据业务需求产生的运行环境定制自己的线程池,我们先来看看如何定制自己的线程池。
一、定制自己的线程池
package com.peace.web.test.threadpool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author x
* @date 2020/12/3 20:54
*/
@Slf4j
public class TestThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 10, 1000,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(9000),
new CustomizableThreadFactory("spring-threadpool-"));
threadPool.allowCoreThreadTimeOut(false);
threadPool.execute(() -> log.info("a"));
threadPool.shutdown();
}
}
第一个参数:int corePoolSize:核心线程数,在线程池线程数量没有达到corePoolSize时,线程数量会伴随任务量等量增加,直到达到corePoolSize,他和初始化时线程数没任何关系;
第二个参数:int maximumPoolSize:当线程数量达到核心线程数量,并且任务队列满了,就会继续创建线程,直到线程数量达到maximum,池中线程数量无论如何不可能超过这个值;
第三个参数:long keepAliveTime:线程存活空闲时间,当线程空闲时间超过这个时间,就可以被销毁,默认只销毁非核心线程数量之外的线程,可通过ThreadPoolExecutor#allowCoreThreadTimeOut(true)方法设置允许销毁核心线程;
第四个参数:TimeUnit unit:keepAliveTime的单位,可设置为小时或分钟等值;
第五个参数:BlockingQueue<Runnable> workQueue:任务队列,是一个阻塞队列(能保证线程安全);
第六个参数:ThreadFactory threadFactory:线程工厂,能够设置线程名字,方便定位生产问题;
第七个参数:RejectedExecutionHandler handler:无法添加任务(队列满,线程数量达到maximum)时的饱和策略,可不传使用默认值。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略:
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
二、提交任务
我们可以使用execute提交的任务,但是execute方法没有返回值,所以无法判断任务知否被线程池执行成功。我们也可以使用submit 方法来提交任务,它会返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
threadPool.execute(() -> log.info("a"));
Future<?> b = threadPool.submit(() -> {
log.info("b");
return "success";
});
try {
String ret = (String) b.get();
log.info("get return:{}", ret);
} catch (InterruptedException | ExecutionException e) {
log.error("", e);
}
threadPool.shutdown();
三、线程池关闭
我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
四、线程池的监控
通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用
taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。
附录1
第一种 CustomizableThreadFactory
Spring 框架提供的 CustomizableThreadFactory
。
ThreadFactory springThreadFactory = new CustomizableThreadFactory("springThread-pool-");
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
exec.submit(() -> {
logger.info("springThreadFactory");
});
Google guava 工具类 提供的 ThreadFactoryBuilder
,使用链式方法创建。
ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("retryClient-pool-").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),guavaThreadFactory );
exec.submit(() -> {
logger.info("guavaThreadFactory");
});
Apache commons-lang3 提供的 BasicThreadFactory
.
ThreadFactory basicThreadFactory = new BasicThreadFactory.Builder()
.namingPattern("basicThreadFactory-").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),basicThreadFactory );
exec.submit(() -> {
logger.info("apacheThreadFactory");
});
最终本质都是 给 java.lang.Thread#name 设置名称,详情源码感兴趣的可以自行查看。
final Thread thread = new Thread();
thread.setName(name);