线程池的七种创建方式
什么是线程池?
-
线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。
-
池化思想在计算机的应用也比较广泛,比如以下这些:
- 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
- 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
- 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
-
线程池的优势主要体现在以下 4 点:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池 ScheduledThreadPool,就允许任务延期执行或定期执行。
线程池的使用
- 线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
- 一类是通过
ThreadPoolExecutor
创建的线程池; - 另一个类是通过
Executors
创建的线程池。
package java.util.concurrent; public class Executors {} public class ThreadPoolExecutor extends AbstractExecutorService {}
- 一类是通过
- 线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):
- Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
- Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
- Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
- Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
- Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
- Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
- ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
ExecutorService 常用方法
public interface Executor {
//在将来的某个时间执行给定的命令。该命令可以在新线程,线程池或调用线程中执行,由 Executor实现。
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
void execute(Runnable command) 在将来的某个时间执行给定的命令。
boolean awaitTermination(long timeout, TimeUnit unit) 阻止所有任务在关闭请求完成后执行,或发生超时或当前线程中断,以先到者为准。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) 执行给定的任务,返回持有他们的状态和结果的所有完成的期货列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,返回在所有完成或超时到期时持有其状态和结果的期货列表,以先发生者为准。
<T> T invokeAny(Collection<? extends Callable<T>> tasks) 执行给定的任务,返回成功完成的结果(即,不抛出异常),如果有的话。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) 执行给定的任务,返回一个已经成功完成的结果(即,不抛出异常),如果在给定的时间超过之前有的话。
boolean isShutdown() 如果这个执行者已被关闭,则返回 true 。
boolean isTerminated() 如果所有任务在关闭后完成,则返回 true 。
void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
List<Runnable> shutdownNow() 尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。
Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
<T> Future<T> submit(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
<T> Future<T> submit(Callable<T> task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。
}
1. FixedThreadPool
- 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
//nThreads - 池中的线程数 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //nThreads - 池中的线程数 threadFactory - 创建新线程时使用的工厂 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
- 练习
@Test public void fixedThreadPool() { //创建 2个数据级的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); //创建线程 Thread thread = new Thread(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); }); //线程池执行任务(一次添加 4个任务) //执行线程的方法有两种:submit 和 execute threadPool.submit(thread); threadPool.execute(thread); threadPool.submit(thread); threadPool.execute(thread); }
2. CachedThreadPool
- 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } //threadFactory - 创建新线程时使用的工厂 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
- 练习
@Test public void cachedThreadPoll() { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { cachedThreadPool.execute(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); }); } }
- 从上述结果可以看出,线程池创建了 10 个新线程来执行相应的任务。
3. SingleThreadExecutor
- 创建单个线程数的线程池,它可以保证先进先出的执行顺序。
//创建一个使用从无界队列运行的单个工作线程的执行程序。 //(请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务, //则新的线程将占用它。)任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。 //与其他等效的newFixedThreadPool(1)不同,返回的执行器保证不被重新配置以使用额外的线程。 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
- 练习
@Test public void SingleThreadPool() { ExecutorService singleThreadPoll = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { int finalI = i; singleThreadPoll.execute(() -> { System.out.println(finalI + ":任务被执行"); }); } }
4. ScheduledThreadPool
- 创建一个可以执行延迟任务的线程池。
//创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
- 练习
- 必须在 main方法上才可以定时,junit不行的
public static void scheduledThreadPool() { ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5); //添加定时任务执行(5s) System.out.println("添加任务,时间:" + new Date()); threadPool.schedule(() -> { System.out.println("任务被执行,时间:" + new Date()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } }, 5, TimeUnit.SECONDS); }
5. SingleThreadScheduledExecutor
- 创建一个单线程的可以执行延迟任务的线程池。
//创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。 //(请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止, //则如果需要执行后续任务,则新的线程将占用它。)任务保证顺序执行,并且不超过一 //个任务将被激活在任何给定的时间。 与其他等效的newScheduledThreadPool(1)不同 //,返回的执行器保证不被重新配置以使用额外的线程。 public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); }
- 练习
- 必须在 main方法上运行,junit不行
public static void singleThreadScheduledPoll() { ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor(); //添加定时任务执行(5s) System.out.println("添加任务,时间:" + new Date()); threadPool.schedule(() -> { System.out.println("任务被执行,时间:" + new Date()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } }, 5, TimeUnit.SECONDS); }
6. newWorkStealingPool
- 创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。
//创建一个维护足够的线程以支持给定的并行级别的线程池,并且可以使用多个队列来减少争用。 //并行级别对应于主动参与或可以从事任务处理的最大线程数。 线程的实际数量可以动态增长和收缩。 //工作窃取池不保证执行提交的任务的顺序。 public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
- 练习
@Test public void newWorkStealingPoll() { ExecutorService threadPool = Executors.newWorkStealingPool(); for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(() -> { System.out.println(finalI + ":被执行,线程:" + Thread.currentThread().getName()); }); } }
- 从上述结果可以看出,"任务的执行顺序是不确定的,因为它是抢占式执行的"。
7. ThreadPoolExecutor
- 最原始的创建线程池的方式,它包含了 7 个参数可供设置(阿里推荐使用的)。
public ThreadPoolExecutor( //核心线程池大小,即使他们处于空闲状态,除非设置 allowCoreThreadTimeOut(允许核心线程超时) int corePoolSize, //线程池最大容量大小 int maximumPoolSize, //最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。 long keepAliveTime, //keepAliveTime参数的时间单位(天,时,分,秒,毫秒,微秒,纳秒) TimeUnit unit, //一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型。 //线程的排队策略与 BlockingQueue有关 //ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。 //LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。 //SynchronousQueue:(常用)一个不存储元素的阻塞队列,即直接提交给线程不保持它们。 //PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。 //DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。 //LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。 //LinkedBlockingDeque:(常用)一个由链表结构组成的双向阻塞队列。 BlockingQueue<Runnable> workQueue, //当执行程序创建新线程使用的工厂(默认为正常优先级,非守护线程) ThreadFactory threadFactory, //(拒绝策略4中)执行被阻止时使用的程序,因为线程限制和队列容量 //AbortPolicy:(默认)拒绝并抛出异常。 //CallerRunsPolicy:使用当前调用的线程来执行此任务。 //DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。 //DiscardPolicy:忽略并抛弃当前任务。 RejectedExecutionHandler handler }
- 练习
public static void threadPool() { ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10)); for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(() -> { System.out.println(finalI + ":被执行,线程:" + Thread.currentThread().getName()); try { Thread.sleep(1000);//1000MS } catch (InterruptedException e) { e.printStackTrace(); } }); } }
线程池的执行流程
https://www.cnblogs.com/javazhiyin/p/11364027.html
http://www.codebaoku.com/tech/tech-yisu-465674.html
- corePoolSize,maximumPoolSize,workQueue之间关系。
- 当前线程池中线程数小于 corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
- 当线程池数达到 corePoolSize时,新提交任务将被放入 workQueue中,等待线程池中任务执行。
- 当 workQueue已满,且 maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
- 当 workQueue已满,且提交任务超过 maximumPollSize,任务由 RejectedExecutionHandler处理。
- 当线程池中线程数超过 corePoolSize,且超过这部分的空闲时间达到 keepAliveTime时,回收这些线程。
- 当设置 allowCoreThreadTimeOut(true)时,线程池中 corePollSize范围内的线程空闲时间达到 keepAliveTimer也将回收。
- 执行流程
- 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。
- 当调用 execute()方法添加一个任务时,线程池会做如下判断
a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建运行这个队列。
b. 如果正在运行的线程数量大于或者等于 corePoolSize,那么将这个任务放入队列。
c. 如果这时候队列满了,而且正在运行的线程数量小于 maximunPollSize,那么线程池会抛出 RejectedExecutionException。 - 当一个线程完成任务时,他会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有的任务完成后,他最终会收缩到 corePoolSize的大小。
线程拒绝策略
https://www.cnblogs.com/skywang12345/p/3512947.html#a21
- AbortPolicy
当任务添加到线程池中被拒绝时,他将抛出 RejectedExecutionException 异常。 - CallerRunsPolicy
当任务添加到线程池中被拒绝时,会在线程池当前正在运行的 Thread线程池中处理被拒绝的任务。 - DiscardOldestPolicy
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的末处理任务就,然后将被拒绝的任务添加到等待队列中。 - DiscardPolicy
当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。 - 练习 DiscardPolicy
public class DiscardPolicyDemo { private static final int CORE_POOL_SIZE = 1;//核心线程数 private static final int MAXIMUM_POOL_SIZE = 1;//最大线程数 private static final int CAPACITY = 1;//容量 public static void main(String[] args) { ThreadPoolExecutor threadPool = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, ///最大线程数可以存活的时间 0, TimeUnit.SECONDS, //数组结构组成的有界阻塞队列 new ArrayBlockingQueue<Runnable>(CAPACITY)); //设置线程池的拒绝策略为“丢弃” threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(() -> { System.out.println(finalI + " : " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } } }
- 结果说明:线程池 pool的核心线程数和最大线程都为1这意味着"线程池能同时运行的任务数量最大只能是1"。线程池pool的阻塞队列是ArrayBlockingQueue,ArrayBlockingQueue是一个有界的阻塞队列,ArrayBlockingQueue的容量为1。这也意味着线程池的阻塞队列只能有一个线程池阻塞等待。根据""中分析的execute()代码可知:线程池中共运行了2个任务。第1个任务直接放到Worker中,通过线程去执行;第2个任务放到阻塞队列中等待。其他的任务都被丢弃了!
自定义拒绝策略
public class CustomRefuseHandler {
public static void main(String[] args) {
// 任务的具体方法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("当前任务被执行,执行时间:" + new Date() +
" 执行线程:" + Thread.currentThread().getName());
try {
// 等待 1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建线程,线程的任务队列的长度为 1
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 执行自定义拒绝策略的相关操作
System.out.println("我是自定义拒绝策略~");
}
});
// 添加并执行 4 个任务
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}