线程池的创建方式有哪些

发布时间:2021-10-21 15:51:25 作者:iii
来源:亿速云 阅读:300

这篇文章主要讲解了“线程池的创建方式有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“线程池的创建方式有哪些”吧!

什么是线程池?

线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

池化思想在计算机的应用也比较广泛,比如以下这些:

线程池的优势主要体现在以下 4 点:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

  3. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

  4. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

  5. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

同时阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

知道了什么是线程池以及为什要用线程池之后,我们再来看怎么用线程池。

线程池使用

线程池的创建方法总共有 7 种,但总体来说可分为 2 类:

线程池的创建方式有哪些

线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor  创建的):

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;

  3. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;

  4. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;

  5. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;

  6. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;

  7. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

  8. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

单线程池的意义从以上代码可以看出 newSingleThreadExecutor 和 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 种可选:

参数 5:workQueue

一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全,它包含以下 7 种类型:

较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关。

参数 6:threadFactory

线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。

参数 7:handler

拒绝策略,拒绝处理任务时的策略,系统提供了 4 种可选:

默认策略为 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开发手册》给我们的答案:

感谢各位的阅读,以上就是“线程池的创建方式有哪些”的内容了,经过本文的学习后,相信大家对线程池的创建方式有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. Java有几种方式使用线程池
  2. 多线程的创建方式有哪些

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

线程池

上一篇:Windows10资源管理器有哪些新特性

下一篇:怎么对iOS蓝牙执行基于覆盖引导的模糊测试

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》