优势
- 构建一个新的线程是有一定代价的, 因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池
- 减少并发线程的数目
Executors
执行器( Executors) 类有许多静态工厂方法用来构建线程池
方法 | 描述 |
newCachedThreadPool | 必要时创建新线程;空闲线程会被保留 60 秒 |
newFixedThreadPool | 该池包含固定数量的线程;空闲线程会一直被保留 |
newSingleThreadExecutor | 只有一个线程的 “ 池”, 该线程顺序执行每一个提交的任务(类似于 Swing 事件分配线程) |
newScheduledThreadPool | 用于预定执行而构建的固定线程池, 替代 java.util.Timer |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程 “ 池” |
newWorkStealingPool (1.8新增) | 创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。并行级别对应于主动参与或可用于进行任务处理的最大线程数。线程的实际数量可以动态地增长和收缩。工作窃取池不能保证执行提交的任务的顺序。 |
newCachedThreadPool 方法构建了一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool 方法构建一个具有固定大小的线程池。 如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放 置到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor 是一个退化了大小为 1 的线程池: 由一个线程执行提交的任务,一个接着一个。这3个方法返回实现了ExecutorService 接口的ThreadPoolExecutor 类的对象。
再将一个 Runnable 对象或 Callable 对象提交给 ExecutorService得到Future
ExecutorService
- Future<?> submit(Runnable task) 使用这样一个对象来调用 isDone、 cancel 或 isCancelled。但是, get 方法在完成的时候只是简单地返回 null。
- Future<T> submit(Runnable task, T result) Future 的 get 方法在完成的时候返回指定的 result 对象。
- Future<T> submit(Callable<T> task) 返回的 Future 对象将在计算结果准备好的时候得到它。
- void shutdown() 关闭服务, 会先完成已经提交的任务而不再接收新的任务。
ThreadPoolExecutor
以Executors的newCachedThreadPool为例,观察内部实例化了一个ThreadPoolExecutor对象。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
观察ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
从结构关系上分析ThreadPoolExecutor继承了AbstractExecutorService抽象类,并实现了ExecutorService接口
从构造参数上分析
- corePoolSize: corePoolSize线程池中要保持的数量,即使它们是空闲的,除非设置了allowCoreThreadTimeOut属性为true
- maximumPoolSize:最大允许在池中允许的最大线程数。
- keepAliveTime:当线程的数量大于核心时,这是空闲线程在终止之前等待新任务的最大时间。(超过核心线程数的线程如果长时间获得不到新任务就会终止掉)
- unit : keepAliveTime的单位 如TimeUnit.SECONDS 代表秒
- workQueue:工作队列(实现BlockingQueue接口的阻塞队列)用于在执行任务之前保存任务的队列。此队列只保留execute方法提交的Runnable任务。常使用的阻塞队列LinkedBlockingQueue、SynchronousQueue、ArrayBlockingQueue
线程的排队策略与阻塞队列相关:
LinkedBlockingQueue:当任务数大于核心线程数时,新任务添加到队列中,当队列容量达到最大时,将创建新线程,直到达到最大线程数maximumPoolSize。(基于链表实现的一个阻塞队列,默认容器大小Integer.MAX_VALUE。)
SynchronousQueue: 当任务数大于核心线程数时,直接创建新的线程直到达到最大线程数maximumPoolSize。(一个没有内部容量的阻塞队列,其中每个插入操作必须等待另一个线程执行相应的移除操作。)
ArrayBlockingQueue : 同LinkedBlockingQueue。(基于数组实现的一个阻塞队列,需要指定容器大小。)
- threadFactory:创建新线程时要使用的工厂。
- handler : 处理程序在执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量。
handler有以下四种策略:
ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常。 (ThreadPoolExecutor默认)
ThreadPoolExecutor.CallerRunsPolicy():由调用线程处理该任务 。
ThreadPoolExecutor.DiscardPolicy():也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
使用
newFixedThreadPool会提前创建好固定数量的线程池
ThreadPoolExecutor pool =(ThreadPoolExecutor)Executors.newFixedThreadPool(3);
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1
newCachedThreadPool 源码中初始线程数为0,当使用时会立即创建线程
ThreadPoolExecutor pool =(ThreadPoolExecutor)Executors.newCachedThreadPool();
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
小结
使用ThreadPoolExecutor构造方法去生成线程池更加灵活,可以参考Spring的ThreadPoolTaskExecutor的实现。