美文网首页
如何设计一个线程池

如何设计一个线程池

作者: 董董呀 | 来源:发表于2024-01-17 09:56 被阅读0次

ThreadPoolExecutor 有哪些常用的方法?

ThreadPoolExecutor有如下常用方法:

submit()/execute():执行线程池
shutdown()/shutdownNow():终止线程池
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程
这些方法可以用来终止线程池、线程池监控等。

submit和 execute两个方法有什么区别?

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

shutdownNow() 和 shutdown() 两个方法有什么区别

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

线程池的工作原理

image.png

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

为什么是先添加队列而不是先创建最大线程

在创建新线程的时候,是要获取全局锁的,这个时候其他的就需要阻塞,影响了整体效率。
就好比一个企业里面有十个(core)正式工的名额,最多招十个正式工(核心线程),要是任务超过正式人数(task>core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这十个人,但是任务可以稍积压一下。即先放到队列中去(代价低)。十个正式工慢慢干,迟早会干完的,如果任务还在持续增加,超过正式工的加班忍耐极限了(队列满了),就招外包(非核心线程)帮忙了,还是正式工加外包还不能完成任务,那么新来的任务就会被领导拒绝(线程池拒绝策略)。

线程池中核心线程数量大小怎么设置

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

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

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

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

线程池为什么要使用阻塞队列而不使用非阻塞队列

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

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

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

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

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

不用阻塞队列也是可以的,不过实现起来比较麻烦而已

线程池状态

RUNNING:线程池的初始化状态,可以添加待执行的任务。
SHUTDOWN:线程池处于待关闭状态,不接收新任务仅处理已经接收的任务。
STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务。
TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理。
TERMINATED:线程池终止状态

线程池中线程复用原理

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

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

线程池几个核心参数

corePoolSize:线程池中的核心线程数
maximumPoolSize:线程池中最大线程数
keepAliveTime:闲置超时时间
unit:keepAliveTime 超时时间的单位(时/分/秒等)
workQueue:线程池中的任务队列
threadFactory:为线程池提供创建新线程的线程工厂
rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

线程池各个参数设置

https://juejin.cn/post/6854573211384823815
https://www.zhihu.com/question/60310190

五种线程池,四种拒绝策略,三类阻塞队列

  • 五种线程池
    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
    threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
    threadPool = Executors.newScheduledThreadPool(2);
    threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
    threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
  • 四种拒绝策略
    RejectedExecutionHandler rejected = null;
    rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
    rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务

  • 三类阻塞队列
    1 有界队列
    workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出(FIFO)队列,支持公平锁和非公平锁,有界
    workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出(FIFO)队列,默认长度为 Integer.MaxValue 有OOM危险,有界
    workQueue = new LinkedBlockingDeque(); //一个由链表结构组成的,双向阻塞队列,有界
    2 无界队列
    workQueue = new PriorityBlockingQueue(); //支持优先级排序的无限队列,默认自然排序,可以实现 compareTo()方法指定排序规则,不能保证同优先级元素的顺序,无界。
    workQueue = new DelayQueue(); //一个使用优先级队列(PriorityQueue)实现的无界延时队列,在创建时可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
    workQueue = new LinkedTransferQueue(); //一个由链表结构组成的,无界阻塞队列
    3 同步移交队列
    workQueue = new SynchronousQueue<>();//无缓冲的等待队列,队列不存元素,每个put操作必须等待take操作,否则无法添加元素,支持公平非公平锁,无界

相关文章

  • 如何设计一个线程池?

    为什么需要线程池 如何设计一个线程池 用C++11实现一个线程池 为什么需要线程池 线程的频繁创建和销毁,不仅会消...

  • 线程池

    JDK线程池 为什么要用线程池 线程池为什么这么设计 线程池原理 核心线程是否能被回收 如何回收空闲线程 Tomc...

  • java----线程池

    什么是线程池 为什么要使用线程池 线程池的处理逻辑 如何使用线程池 如何合理配置线程池的大小 结语 什么是线程池 ...

  • 线程池

    线程池的实现原理当向线程池提交一个任务之后,线程池是如何处理这个任务呢?(1)线程池判断核心线程池里的线程是否都在...

  • Java扫盲

    如何设计一个mq,jvm内存模型,动态代理原理,线程池核心参数,饱和策略,mybatis 原理;高可用如何实现; ...

  • java 线程池设计模式

    java 线程池采用的是 Thread Pool 线程池模式。 线程池设计模式主要解决在资源有限的情况下为每一个任...

  • 20.多线程总结(七)-ThreadPoolExecutor线程

    1.如何创建一个线程池? 2.线程池运行机制 a.new线程池时,线程池工作队列中已经被添加的Runnable是否...

  • Java线程池的实现原理

    线程池的实现原理 当向线程池提交一个任务之后,线程池时如何处理的呢?让我们来看一下线程池的主要处理流程: 线程池判...

  • java工程师面试题大全(持续更新)

    问:说一下线程池,线程池是如何执行任务的答:线程池的设计不同于一般意义上的池化资源。一般意义上的池化资源是需要时从...

  • 线程池

    线程池解决的核心问题:资源管理问题。 线程池运行机制最主要的三个点: 线程池如何维护自身状态; 线程池如何管理任务...

网友评论

      本文标题:如何设计一个线程池

      本文链接:https://www.haomeiwen.com/subject/rmulodtx.html