返回顶部
首页 > 资讯 > 精选 >为什么要用线程池
  • 625
分享到

为什么要用线程池

2023-06-15 18:06:33 625人浏览 安东尼
摘要

这篇文章主要讲解了“为什么要用线程池”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“为什么要用线程池”吧!下面是一段创建线程并运行的代码:for (int i =

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

下面是一段创建线程并运行的代码:

for (int i = 0; i < 100; i++) {     new Thread(() -> {         System.out.println("run thread->" + Thread.currentThread().getName());         userService.updateUser(....);     }).start(); }

我们想使用这种方式去做异步,或者提高性能,然后将某些耗时操作放入一个新线程去运行。

这种思路是没问题的,但是这段代码是存在问题的,有哪些问题呢?下面我们就来看看有哪些问题;

  • 创建销毁线程资源消耗;我们使用线程的目的本是出于效率考虑,可以为了创建这些线程却消耗了额外的时间,资源,对于线程的销毁同样需要系统资源。

  • cpu资源有限,上述代码创建线程过多,造成有的任务不能即时完成,响应时间过长。

  • 线程无法管理,无节制地创建线程对于有限的资源来说似乎成了“得不偿失”的一种作用。

既然我们上面使用手动创建线程会存在问题,那有解决方法吗?

答案:有的,使用线程池。

线程池介绍

线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。

线程池有什么优点?

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性。

线程池使用

jdk中rt.jar包下JUC(java.util.concurrent)创建线程池有两种方式:ThreadPoolExecutor 和  Executors,其中 Executors又可以创建 6 种不同的线程池类型。

ThreadPoolExecutor 的使用

线程池使用代码如下:

import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;  public class ThreadPoolDemo {     private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));      public static void main(String[] args) {         threadPoolExecutor.execute(new Runnable() {             @Override             public void run() {                 System.out.println("田先生您好");             }         });     } }

以上程序执行结果如下:

田先生您好

核心参数说明

ThreadPoolExecutor的构造方法有以下四个:

为什么要用线程池

可以看到最后那个构造方法有 7  个构造参数,其实前面的三个构造方法只是对最后那个方法进行包装,并且前面三个构造方法最终都是调用最后那个构造方法,所以我们这里就来聊聊最后那个构造方法。

参数解释

corePoolSize

线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut  属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。

maximumPoolSize

最大线程数,当线程不够时能够创建的最大线程数。

keepAliveTime

线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的  allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。

unit

配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。

workQueue

线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。

threadFactory

为线程池提供创建新线程的线程工厂。

rejectedExecutionHandler

线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution  方法,可在此方法内添加任务超出最大值的事件处理。ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:

  • DiscardPolicy():丢弃掉该任务,不进行处理。

  • DiscardOldestPolicy():丢弃队列里最近的一个任务,并执行当前任务。

  • AbortPolicy():直接抛出 RejectedExecutionException 异常(默认)。

  • CallerRunsPolicy():既不抛弃任务也不抛出异常,直接使用主线程来执行此任务。

包含所有参数的使用案例:

public class ThreadPoolExecutorTest {     public static void main(String[] args) throws InterruptedException, ExecutionException {         ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,                 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),                 new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());         threadPool.allowCoreThreadTimeOut(true);         for (int i = 0; i < 10; i++) {             threadPool.execute(new Runnable() {                 @Override                 public void run() {                     System.out.println(Thread.currentThread().getName());                     try {                         Thread.sleep(2000);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }             });         }     } } class MyThreadFactory implements ThreadFactory {     private AtomicInteger count = new AtomicInteger(0);     @Override     public Thread newThread(Runnable r) {         Thread t = new Thread(r);         String threadName = "MyThread" + count.addAndGet(1);         t.setName(threadName);         return t;     } }

运行输出:

main MyThread1 main MyThread1 MyThread1 ....

这里仅仅是为了演示所有参数自定义,并没有其他用途。

execute() 和 submit()的使用

execute() 和 submit() 都是用来执行线程池的,区别在于 submit() 方法可以接收线程池执行的返回值。

下面分别来看两个方法的具体使用和区别:

// 创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100)); // execute 使用 threadPoolExecutor.execute(new Runnable() {     @Override     public void run() {         System.out.println("老田您好");     } }); // submit 使用 Future<String> future = threadPoolExecutor.submit(new Callable<String>() {     @Override     public String call() throws Exception {         System.out.println("田先生您好");         return "返回值";     } }); System.out.println(future.get());

以上程序执行结果如下:

老田您好 田先生您好 返回值

Executors

Executors 执行器创建线程池很多基本上都是在 ThreadPoolExecutor  构造方法上进行简单的封装,特殊场景根据需要自行创建。可以把Executors理解成一个工厂类 。Executors可以创建 6 种不同的线程池类型。

下面对这六个方法进行简要的说明:

newFixedThreadPool

创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。

newCacheThreadPool

短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60  秒没被使用,则会被移除缓存。如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有  60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

newScheduledThreadPool

创建一个数量固定的线程池,支持执行定时性或周期性任务。

newWorkStealingPool

Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器CPU 处理器数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。

newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newSingleThreadScheduledExecutor

此线程池就是单线程的 newScheduledThreadPool。

线程池如何关闭?

线程池关闭,可以使用 shutdown() 或 shutdownNow() 方法,它们的区别是:

  • shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown  方法之后,线程池就不会再接受新任务了。

  • shutdownNow():执行该方法,线程池的状态立刻变成 STOP  状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。

下面用代码来模拟 shutdown() 之后,给线程池添加任务,代码如下:

import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger;  public class ThreadPoolExecutorAllArgsTest {    public static void main(String[] args) throws InterruptedException, ExecutionException {        //创建线程池        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,                10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2),                new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());        threadPoolExecutor.allowCoreThreadTimeOut(true);        //提交任务        threadPoolExecutor.execute(() -> {            for (int i = 0; i < 3; i++) {                System.out.println("提交任务" + i);                try {                    Thread.sleep(3000);                } catch (InterruptedException e) {                    System.out.println(e.getMessage());                }            }        });        threadPoolExecutor.shutdown();        //再次提及任务        threadPoolExecutor.execute(() -> {            System.out.println("我想再次提及任务");        });    } }

以上程序执行结果如下:

提交任务0 提交任务1 提交任务2

可以看出,shutdown() 之后就不会再接受新的任务了,不过之前的任务会被执行完成。

面试题

面试题1:ThreadPoolExecutor 有哪些常用的方法?

  • ThreadPoolExecutor有如下常用方法:

  • submit()/execute():执行线程池

  • shutdown()/shutdownNow():终止线程池

  • isshutdown():判断线程是否终止

  • getActiveCount():正在运行的线程数

  • getCorePoolSize():获取核心线程数

  • getMaximumPoolSize():获取最大线程数

  • getQueue():获取线程池中的任务队列

  • allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程

这些方法可以用来终止线程池、线程池监控等。

面试题2:说说submit(和 execute两个方法有什么区别?

submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit()  可以使用 Future 接收线程池执行的返回值。

说说线程池创建需要的那几个核心参数的含义

ThreadPoolExecutor 最多包含以下七个参数:

  • corePoolSize:线程池中的核心线程数

  • maximumPoolSize:线程池中最大线程数

  • keepAliveTime:闲置超时时间

  • unit:keepAliveTime 超时时间的单位(时/分/秒等)

  • workQueue:线程池中的任务队列

  • threadFactory:为线程池提供创建新线程的线程工厂

  • rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

面试题3:shutdownNow() 和 shutdown() 两个方法有什么区别?

shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown()  程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown()  之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出  java.lang.InterruptedException: sleep interrupted 异常。

面试题6:了解过线程池的工作原理吗?

为什么要用线程池

当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。

面试题5:线程池中核心线程数量大小怎么设置?

「CPU密集型任务」:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU  计算。尽量使用较小的线程池,一般为CPU核心数+1。因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

「IO密集型任务」:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO  操作比较耗时,会占用比较多时间。可以使用稍大的线程池,一般为2*CPU核心数。IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

另外:线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;

以上只是理论值,实际项目中建议在本地或者测试环境进行多次调优,找到相对理想的值大小。

面试题7:线程池为什么需要使用(阻塞)队列?

主要有三点:

  • 因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。

  • 创建线程池的消耗较高。

面试题8:线程池为什么要使用阻塞队列而不使用非阻塞队列?

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。

使得在线程不至于一直占用cpu资源。

(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如下

while (task != null || (task = getTask()) != null) {})。

不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢?

面试题9:了解线程池状态吗?

通过获取线程池状态,可以判断线程池是否是运行状态、可否添加新的任务以及优雅地关闭线程池等。

为什么要用线程池

RUNNING:线程池的初始化状态,可以添加待执行的任务。

SHUTDOWN:线程池处于待关闭状态,不接收新任务仅处理已经接收的任务。

STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务。

TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理。

TERMINATED:线程池终止状态。

面试题10:知道线程池中线程复用原理吗?

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用  Thread.start()  来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将  run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。

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

--结束END--

本文标题: 为什么要用线程池

本文链接: https://lsjlt.com/news/281235.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 为什么要用线程池
    这篇文章主要讲解了“为什么要用线程池”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“为什么要用线程池”吧!下面是一段创建线程并运行的代码:for (int i =...
    99+
    2023-06-15
  • 为什么要禁用Executors创建线程池
    本篇内容介绍了“为什么要禁用Executors创建线程池”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!线程...
    99+
    2024-04-02
  • Java创建线程池为什么一定要用ThreadPoolExecutor
    目录先说结论OOM风险演示内存溢出原因分析使用ThreadPoolExecutor来改进其他创建线程池的问题总结前言: 在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的...
    99+
    2024-04-02
  • 线程池是什么?线程池(ThreadPoolExecutor)使用详解
    点一点,了解更多https://www.csdn.net/ 本篇文章将详细讲解什么是线程池,线程池的参数介绍,线程池的工作流程,使用Executors创建常见的线程池~~~ 目录 点一点,了解更多 文章目录 一、线程池的概念 1.1线...
    99+
    2023-09-03
    java 数据结构 jvm 面试 java-ee
  • redis为什么要采用单线程
    redis为什么要采用单线程?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。不需要各种锁的性能消耗      ...
    99+
    2024-04-02
  • 什么是java线程池
    使用线程池的好处有很多,比如节省系统资源的开销,节省创建和销毁线程的时间等,当我们需要处理的任务较多时,就可以使用线程池,可能还有很多用户不知道Java线程池如何使用?今天给大家分享Java四种线程池的使用方法。线程池介绍:线程池是一种多线...
    99+
    2017-06-28
    java入门 java 线程池
  • java线程池是什么
    java的线程池是什么,有哪些类型,作用分别是什么 (推荐学习:java课程)线程池是一种多线程处理形式,处理过程中将任务添加队列,然后在创建线程后自动启动这些任务,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处...
    99+
    2016-06-22
    java教程 java
  • Android线程池是什么
    本篇内容主要讲解“Android线程池是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android线程池是什么”吧!我们都知道线程池的用法,一般就是先new一个ThreadPoolExec...
    99+
    2023-06-22
  • SpringBoot线程池和Java线程池怎么使用
    这篇文章主要介绍“SpringBoot线程池和Java线程池怎么使用”,在日常操作中,相信很多人在SpringBoot线程池和Java线程池怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”SpringB...
    99+
    2023-07-06
  • Python自带的线程池和进程池有什么用
    这篇文章主要介绍“Python自带的线程池和进程池有什么用”,在日常操作中,相信很多人在Python自带的线程池和进程池有什么用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”...
    99+
    2024-04-02
  • Redis6.0为什么要引入多线程
    这篇文章给大家分享的是有关Redis6.0为什么要引入多线程的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。Redis6.0之前为什么采用单线程模型严格地说,从Redis 4.0之...
    99+
    2024-04-02
  • Android中的线程和线程池有什么作用
    今天小编给大家分享一下Android中的线程和线程池有什么作用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。前言从用途上来说...
    99+
    2023-07-04
  • Linux C下线程池有什么用
    这篇文章主要介绍了Linux C下线程池有什么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。线程池也是多线程的处理方式。是将“生产者”线程提出任务添加到“任务队列”,然后一...
    99+
    2023-06-15
  • golang线程池和协程池有什么区别
    Golang中没有线程池的概念,而是通过协程(goroutine)来实现并发。协程是一种轻量级的线程,由Go语言的运行时环境(run...
    99+
    2023-10-26
    golang
  • java中什么是线程池
    本篇文章为大家展示了java中什么是线程池,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. ...
    99+
    2023-06-14
  • springboot使用线程池方法是什么
    本篇内容主要讲解“springboot使用线程池方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“springboot使用线程池方法是什么”吧!线程池创建@Configuration@En...
    99+
    2023-06-22
  • Java线程池怎么用
    小编给大家分享一下Java线程池怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、什么是线程池?线程池是一种用于实现计算机程序并发执行的软件设计模式。线程池...
    99+
    2023-06-15
  • python线程池有什么优点
    这篇文章给大家分享的是有关python线程池有什么优点的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是...
    99+
    2023-06-14
  • c++11线程为什么需要互斥量
    这篇文章给大家分享的是有关c++11线程为什么需要互斥量的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。为什么需要互斥量在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里...
    99+
    2023-06-15
  • 线程池的原理和作用是什么
    这篇文章主要讲解了“线程池的原理和作用是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“线程池的原理和作用是什么”吧!一、为什么需要线程池在实际使用中,线...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作