为什么要使用线程池创建线程?
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
为什么线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式?
Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
以上内容摘自阿里巴巴Java开发手册-编程规约-并发处理
下面用一个简单的例子演示使用ThreadPoolExecutor创建一个线程池
public static void main(String[] args){
//创建线程工厂
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("test-pool-%d").build();
//创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 0L,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
//执行10个线程
for(int i=0;i<10;i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//关闭线程池,不在接受新任务
threadPool.shutdown();
}
我们先看ThreadPoolExecutor的构造方法,ThreadPoolExecutor提供了三个构造方法,我们看参数最全的这个:
/**
* 参数意义:
* corePoolSize:核心线程数量,线程池中常存的线程数量,即使这些线程是空闲状态;
* 但如果我们设置了allowCoreThreadTimeOut这个参数为true,
* 这时核心线程将在空闲超过keepAliveTime后被终止
* maximumPoolSize:最大线程数量,即线程池中允许的最大线程数
* keepAliveTime:多余线程存活时间,当池中线程数大于核心数时,多余空闲线程处于空闲状态的时间不会超过这个时间,
* 即多余的空闲线程超过这个时间就会被终止
* unit:keepAliveTime参数的时间单位
* workQueue:任务队列,BlockingQueue类型,该队列用来保存execute方法提交的Runnable任务;
* 如果池中没有空闲的线程是,新任务将会保存到这个队列中,等待有空闲的线程后再执行。
* threadFactory:线程工厂,用于线程池中创建新线程
* handler:如果线程数量已经最大、任务队列已经满了,拒绝新任务的处理策略
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
@NotNull java.util.concurrent.TimeUnit unit,
@NotNull java.util.concurrent.BlockingQueue<Runnable> workQueue,
@NotNull java.util.concurrent.ThreadFactory threadFactory,
@NotNull java.util.concurrent.RejectedExecutionHandler handler)
下面我们对workQueue、threadFactory、handler三个参数详细介绍:
1、workQueue:BlockingQueue类型参数一个阻塞队列,主要用于缓冲任务;
关于阻塞队列可以查看我的另一篇文章:Java阻塞队列—BlockingQueue
2、threadFactory:ThreadFactory类型参数,用来告诉线程池怎么来创建线程
ThreadFactory接口中定义了方法 Thread newThread(Runnable r);通过实现该接口我们可以定义接口的名称、优先级、是否为守护线程等,我们可以自己实现也可以采用第三方的实现。
我们参考一下JDK中 Executors类中的DefaultThreadFactory实现,DefaultThreadFactory的源码如下:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
//设置线程组同当前线程一致
group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();
//线程名称前缀
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
//设为非守护线程
if (t.isDaemon())
t.setDaemon(false);
//线程的优先级设为默认的5
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
我们使用Executors创建的FixedThreadPool、SingleThreadPool、 CachedThreadPool 和 ScheduledThreadPool就是采用的这个DefaultThreadFactory类。
我们也可用第三方实现的线程创建工厂,如google的ThreadFactoryBuilder
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("request-pool-%d")
.setPriority(1)
.build();
3、 handler:RejectedExecutionHandler类型参数,线程池拒绝新任务的处理策略
在ThreadPoolExecutor中有四个RejectedExecutionHandler的实现,分别是:
- AbortPolicy:丢掉任务,抛出RejectedExecutionException,是线程池的默认策略(jdk代码)
- DiscardPolicy:直接丢弃,不做任何处理
- DiscardOldestPolicy:删掉队列头部的任务,再尝试加入队列
- CallerRunsPolicy:使用主线程执行该任务
ThreadPoolExecutor常用的方法:
//让线程池执行指定的任务,不一定是立即执行
void execute(Runnable command)
//向线程池中提交指定的任务,如果任务执行成功,则返回的Future对象get得到的将为null
Future<?> submit(Runnable task)
//向线程池中提交指定的任务,如果任务执行成功,返回的Future对象get得到的为任务的返回值
<T> Future<T> submit(Callable<T> task)
//向线程池中提交指定的任务,如果任务执行成功,返回的Future对象get得到的就是入参result
<T> Future<T> submit(Runnable task, T result)
//关闭线程池;该方法不会立即终止已经提交的任务,执行该方法后线程池将不再接收新任务,待池中的所有任务执行完成后才会真正的关闭线程池
void shutdown()
//阻塞主线程指定的时间;如果池中任务全部完成返回true阻塞终止,如果超时返回false阻塞终止,主线程如果被中断抛出异常阻塞终止
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException