本文主要讲解“创建线程池的方法有哪些”,文中的讲解内容简单明了,易学易懂。请跟随边肖的思路,深入其中,一起学习学习“线程池的创建方法有哪些”!
什么是线程池?
ThreadPool是一种基于池化思想的管理和使用线程的机制。它预先将多个线程存储在一个“池”中,这样当一个任务出现时,可以避免重新创建和销毁线程带来性能开销,只需要从“池”中取出相应的线程来执行相应的任务。
汇集想法也广泛应用于计算机,例如:
内存池:提前申请内存,提高申请内存的速度,减少内存碎片。
连接池:提前申请数据库连接,提高了申请连接的速度,降低了系统的开销。
对象池:回收对象,以减少初始化和发布过程中昂贵的资源损失。
线程池的优势主要体现在以下四点:
鸿蒙系统正式战略合作,打造——HarmonyOS技术社区。
降低资源消耗:通过池技术重用创建的线程,以减少线程创建和销毁造成的损失。
提高响应速度:当任务到达时,它可以立即执行,而无需等待线程被创建。
提高线程的可管理性:线程是稀缺资源。如果无限制地创建它们,不仅会消耗系统资源,还会由于线程分配不合理而导致资源调度不均衡,降低系统的稳定性。线程池可用于统一分配、调优和监控。
提供更多更强大的功能:线程池是可扩展的,允许开发人员向其中添加更多功能。例如,延迟定时线程池ScheduledThreadPoolExecutor允许延迟或定期执行任务。
同时,阿里巴巴也在其《Java开发手册》中强制要求线程资源必须通过线程池提供,不允许在应用中明确创建线程。
说明:线程池的优势在于减少了创建和销毁线程所消耗的时间和系统资源的开销,解决了资源不足的问题。如果不使用线程池,可能会导致系统创建大量同类线程,导致内存消耗或“过度切换”的问题。
在了解了什么是线程池以及为什么要使用它之后,让我们来看看如何使用它。
线程池使用
创建线程池有七种方法,但可以分为两类:
一个是ThreadPoolExecutor创建的线程池。
另一个类是由执行器创建的线程池。
创建线程池有七种方法(其中6种由执行器创建,1种由线程池执行器创建):
鸿蒙系统正式战略合作,打造——HarmonyOS技术社区。
Executors.newFixedThreadPool:创建一个固定大小的线程池,可以控制并发线程的数量,多余的线程会在队列中等待;
Executors.newCachedThreadPool:创建一个可缓存的线程池。如果线程数量超过处理要求,缓存将在一段时间后回收。如果线程数量不够,请创建一个新线程。
执行者。newsingletthreadexecutor:创建单线程号的线程池,可以保证先进先出的执行顺序;
executors . newscheduledthreadpool:创建一个可以执行延迟任务的线程池;
执行者。newsingletthreadschedule executor:创建可以执行延迟任务的单线程线程池;
Executors.newWorkStealingPool:为抢先执行创建一个线程池(任务执行顺序不确定)[JDK 1.8补充]。
ThreadPoolExecutor:创建线程池最原始的方式,包含7个参数进行设置,后面会详细介绍。
单线程池的意义可以从上面的代码中看到新闻。
eThreadExecutor 和 newSingleThreadScheduledExecutor 创建的都是单线程池,那么单线程池的意义是什么呢?答:虽然是单线程池,但提供了工作队列,生命周期管理,工作线程维护等功能。
那接下来我们来看每种线程池创建的具体使用。
1.FixedThreadPool
创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
使用示例如下:
public static void fixedThreadPool() { // 创建 2 个数据级的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 创建任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); } }; // 线程池执行任务(一次添加 4 个任务) // 执行任务的方法有两种:submit 和 execute threadPool.submit(runnable); // 执行方式 1:submit threadPool.execute(runnable); // 执行方式 2:execute threadPool.execute(runnable); threadPool.execute(runnable); }
执行结果如下:
如果觉得以上方法比较繁琐,还用更简单的使用方法,如下代码所示:
public static void fixedThreadPool() { // 创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(2); // 执行任务 threadPool.execute(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); }); }
2.CachedThreadPool
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
使用示例如下:
public static void cachedThreadPool() { // 创建线程池 ExecutorService threadPool = Executors.newCachedThreadPool(); // 执行任务 for (int i = 0; i < 10; i++) { threadPool.execute(() -> { System.out.println("任务被执行,线程:" + Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }); } }
执行结果如下:
从上述结果可以看出,线程池创建了 10 个线程来执行相应的任务。
3.SingleThreadExecutor
创建单个线程数的线程池,它可以保证先进先出的执行顺序。
使用示例如下:
public static void singleThreadExecutor() { // 创建线程池 ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 执行任务 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + ":任务被执行"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }); } }
执行结果如下:
4.ScheduledThreadPool
创建一个可以执行延迟任务的线程池。
使用示例如下:
public static void scheduledThreadPool() { // 创建线程池 ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5); // 添加定时执行任务(1s 后执行) System.out.println("添加任务,时间:" + new Date()); threadPool.schedule(() -> { System.out.println("任务被执行,时间:" + new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }, 1, TimeUnit.SECONDS); }
执行结果如下:
从上述结果可以看出,任务在 1 秒之后被执行了,符合我们的预期。
5.SingleThreadScheduledExecutor
创建一个单线程的可以执行延迟任务的线程池。
使用示例如下:
public static void SingleThreadScheduledExecutor() { // 创建线程池 ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor(); // 添加定时执行任务(2s 后执行) System.out.println("添加任务,时间:" + new Date()); threadPool.schedule(() -> { System.out.println("任务被执行,时间:" + new Date()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } }, 2, TimeUnit.SECONDS); }
执行结果如下:
从上述结果可以看出,任务在 2 秒之后被执行了,符合我们的预期。
6.newWorkStealingPool
创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8+ 版本中才能使用。
使用示例如下:
public static void workStealingPool() { // 创建线程池 ExecutorService threadPool = Executors.newWorkStealingPool(); // 执行任务 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName()); }); } // 确保任务执行完成 while (!threadPool.isTerminated()) { } }
执行结果如下:
从上述结果可以看出,任务的执行顺序是不确定的,因为它是抢占式执行的。
7.ThreadPoolExecutor
最原始的创建线程池的方式,它包含了 7 个参数可供设置。
使用示例如下:
public static void myThreadPoolExecutor() { // 创建线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10)); // 执行任务 for (int i = 0; i < 10; i++) { final int index = i; threadPool.execute(() -> { System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } }
执行结果如下:
ThreadPoolExecutor 参数介绍
ThreadPoolExecutor 最多可以设置 7 个参数,如下代码所示:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { // 省略... }
7 个参数代表的含义如下:
参数 1:corePoolSize
核心线程数,线程池中始终存活的线程数。
参数 2:maximumPoolSize
最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
参数 3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
参数 4:unit:
单位是和参数 3 存活时间配合使用的,合在一起用于设定线程的存活时间 ,参数 keepAliveTime 的时间单位有以下 7 种可选:
-
TimeUnit.DAYS:天
-
TimeUnit.HOURS:小时
-
TimeUnit.MINUTES:分
-
TimeUnit.SECONDS:秒
-
TimeUnit.MILLISECONDS:毫秒
-
TimeUnit.MICROSECONDS:微妙
-
TimeUnit.NANOSECONDS:纳秒
参数 5:workQueue
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:
-
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
-
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
-
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
-
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
-
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
-
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
-
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。
参数 6:threadFactory
线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。
参数 7:handler
拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:
-
AbortPolicy:拒绝并抛出异常。
-
CallerRunsPolicy:使用当前调用的线程来执行此任务。
-
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
-
DiscardPolicy:忽略并抛弃当前任务。
默认策略为 AbortPolicy。
线程池的执行流程
ThreadPoolExecutor 关键节点的执行流程如下:
-
当线程数小于核心线程数时,创建线程。
-
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
-
当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。
线程池的执行流程如下图所示:
线程拒绝策略
我们来演示一下 ThreadPoolExecutor 的拒绝策略的触发,我们使用 DiscardPolicy 的拒绝策略,它会忽略并抛弃当前任务的策略,实现代码如下:
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 ThreadPoolExecutor.DiscardPolicy()); // 添加并执行 4 个任务 threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); threadPool.execute(runnable); }
我们创建了一个核心线程数和最大线程数都为 1 的线程池,并且给线程池的任务队列设置为 1,这样当我们有 2 个以上的任务时就会触发拒绝策略,执行的结果如下图所示:
从上述结果可以看出只有两个任务被正确执行了,其他多余的任务就被舍弃并忽略了。其他拒绝策略的使用类似,这里就不一一赘述了。
自定义拒绝策略
除了 Java 自身提供的 4 种拒绝策略之外,我们也可以自定义拒绝策略,示例代码如下:
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); }
程序的执行结果如下:
究竟选用哪种线程池?
经过以上的学习我们对整个线程池也有了一定的认识了,那究竟该如何选择线程池呢?
我们来看下阿里巴巴《Java开发手册》给我们的答案:
-
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
-
说明:Executors 返回的线程池对象的弊端如下:
-
1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
-
2)CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
-
所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。
感谢各位的阅读,以上就是“线程池的创建方式有哪些”的内容了,经过本文的学习后,相信大家对线程池的创建方式有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/39940.html