由于设计与操作系统的交互,构建一个新的线程开销一般较大。当任务数量较多时,可以创建一个包含一定数量线程的线程缓存池,如果其中某个线程完成了一个任务的执行,可以给其再行分配一个新的任务让其接着执行,从而避免了创建过多的线程。当线程的任务运行完毕后,这个线程不会终止,而是留在池中准备为下一个请求提供服务。
线程池的创建
ExecutorService为表示线程池的接口,可以使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象。
//ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数 | 说明 |
---|---|
corePoolSize | 核心线程数量 |
maximumPoolSize | 最大线程数量,包括核心线程和临时线程 |
keepAliveTime | 临时线程存活时间 |
unit | 临时线程存活时间单位 |
workQueue | 任务队列 |
threadFactory(可选) | 线程创建方式 |
handler(可选) | 任务拒绝策略 |
此外,创建线程池还有一种方法,可以通过Executors线程池工具类调用方法返回不同特点的线程池对象,但“大厂”一般强制不允许使用Executors创建线程池,而是通过ThreadPoolExecutor的方式。
在大型并发系统环境中使用Executors如果不注意可能会出现系统风险,如Executors返回的线程池对象存在部分参数设置为Integer.MAX_VALUE,可能会堆积大量请求或创建大量线程。
部分参数说明
核心线程和临时线程
核心线程数量:一般来说,对于计算密集型任务的核心线程数量会设置为CPU核数+1,对于IO密集型任务的核心线程数量会设置为CPU核数*2
创建临时线程:当新任务提交时发现核心线程都在忙,任务队列也满了,并且当前线程数量未达到最大线程数量,此时才会创建临时线程。
任务队列
BlockingQueue是表示阻塞队列的一个接口,任务队列是基于阻塞队列实现的,即采用生产者和消费者的模式。
Java中提供了7种BlockingQueue的实现类:
实现类 | 说明 |
---|---|
ArrayBlockingQueue | 数组结构组成的有界阻塞队列 |
DelayQueue | 链表结构组成的有界阻塞队列 |
LinkedBlockingDeque | 支持优先级排序的无界阻塞队列 |
LinkedBlockingQueue | 二叉堆实现的无界优先级阻塞队列 |
LinkedTransferQueue | 不存储元素的阻塞队列 |
PriorityBlockingQueue | 使用双向队列实现的有界双端阻塞队列 |
SynchronousQueue | 高效链表实现的无界阻塞队列 |
线程创建方式
ThreadFactory是表示线程创建方式的一个接口。
最简单的一个实现类如下:
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
如果构造方法中没有指定创建方式,系统将会使用默认的创建方式Executors.defaultThreadFactory() :
private 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() {
@SuppressWarnings("removal")
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);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
任务拒绝策略
拒绝新任务:已经达到最大线程数量,核心线程和临时线程都在忙,任务队列也满了,此时新任务到来时会拒绝。
任务拒绝策略要求实现RejectedExecutionHandler接口。
Java中提供了4种RejectedExecutionHandler的实现类:
实现类 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy(默认) | 丢弃任务并抛出RejectedExecutionException 异常 |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程处理该任务 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃任务,但是不抛出异常,可以配合这种模式进行自定义的处理方式 |
ThreadPoolExecutor.DiscardPolicy | 丢弃队列最早的未处理任务,然后重新尝试执行任务 |
线程池常用方法
方法 | 说明 |
---|---|
void execute(Runnable command) | 执行Runnable任务 |
Future<T> submit(Callable<T> task) | 执行Callable任务,返回任务对象用于获取线程返回的结果 |
void shutdown | 等全部任务执行完毕后再关闭线程池 |
List<Runnable> shutdownNow() | 立即关闭线程池,返回队列中未执行的任务 |
boolean isTerminated() | 如果所有任务在关闭后都已完成则返回true |
boolean isShutdown() | 如果线程池已关闭则返回true |
//运用线程池实现对同一任务对象进行20个计数1000的线程
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
CountClass count = new CountClass();
Runnable runnable = new MyRunnable(count);
ExecutorService threadPool = new ThreadPoolExecutor(17, 20, 1000,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 20; i++) {
threadPool.execute(runnable);
}
threadPool.shutdown();
while (!threadPool.isTerminated());
System.out.println(count.getCountNum());
}
}
class MyRunnable implements Runnable {
private CountClass count;
public MyRunnable(CountClass count){
this.count = count;
}
@Override
public void run() {
count.countAdd();
}
}
class CountClass{
private int countNum=0;
public void countAdd(){
for (int i = 0; i < 1000; i++) {
synchronized (this) {
countNum++;
}
}
}
public int getCountNum() {
return countNum;
}
}