线程池原理_直击线程池底层原理
发布日期:2021-06-24 12:47:10 浏览次数:2 分类:技术文章

本文共 4508 字,大约阅读时间需要 15 分钟。

1ae7720247d89fc6fb13e1c9c16fcdf8.png

什么是线程池

很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。

b164daa5de2fb0b3ea47b1225b512a6d.png

线程池的优点

.我们知道不用线程池的话,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

总体来说,线程池的优势如下:

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

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

(3)提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的使用

在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。

ublic ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);}public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler;}

corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。

maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。

keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。

unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

workQueue(必需):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。

threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程工作流程

1、如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务。

2、如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。

3、如果工作队列workQueue也满时:当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。

// 创建线程池 Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,

MAXIMUM_POOL_SIZE,

KEEP_ALIVE,

TimeUnit.SECONDS,

sPoolWorkQueue,

sThreadFactory);

// 向线程池提交任务 threadPool.execute(new Runnable() {

@Override

public void run() {

… // 线程执行的任务

}

});

// 关闭线程池

threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程

threadPool.shutdownNow(); //设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

线程池工作原理

其工作原理流程图如下:

8f8db8ce3628b72de0868c248e81466a.png

线程池的分类

Executors是jdk里面提供的创建线程池的工厂类,它默认提供了4种常用的线程池应用,而不必我们去重复构造。

  1. 定长线程池(FixedThreadPool)
  2. 定时线程池(ScheduledThreadPool )
  3. 可缓存线程池(CachedThreadPool)
  4. 单线程化线程池(SingleThreadExecutor)

FixedThreadPool

078fe62af15b129080eb836c27f2b616.png

特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。

应用场景:控制线程最大并发数。

CachedThreadPool

4ee6b5cdff232fb1595316bb363550d8.png

特点:无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列为不存储元素的阻塞队列。

应用场景:执行大量、耗时少的任务。

SingleThreadExecutor

4338a08d872ae5001b53e7d87c284219.png

特点:只有1个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。

应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。

ScheduledThreadPool

a34d63ee8dbd6f10a1721c48289133b6.png

特点:核心线程数量固定,非核心线程数量无限,执行完闲置10ms后回收,任务队列为延时阻塞队列。

应用场景:执行定时或周期性的任务。

四种对比

f6645dbf1b44fdf89b9c3bd9a9201514.png

拒绝策略

  1. AbortPolicy:简单粗暴,直接抛出拒绝异常,这也是默认的拒绝策略。
  2. CallerRunsPolicy: 如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。
  3. DiscardPolicy: 从方法看没做任务操作,即表示不处理新任务,即丢弃。
  4. DiscardOldestPolicy:抛弃最老的任务,就是从队列取出最老的任务然后放入新的任务进行执行。

如何提交和关闭线程

提交

如可以先随便定义一个固定大小的线程池

ExecutorService es = Executors.newFixedThreadPool(3);

提交一个线程

es.submit(xxRunnble);

es.execute(xxRunnble);

submit和execute分别有什么区别呢?

execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。

submit返回一个Future对象,如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。

关闭

es.shutdown();

不再接受新的任务,之前提交的任务等执行结束再关闭线程池。

es.shutdownNow();

不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list列表。

总结

其实Executors的4个功能线程有如下弊端:

  1. FixedThreadPool和SingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue,可能会耗费非常大的内存,甚至OOM。
  2. CachedThreadPool和ScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

建议大家直接用ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

转载地址:https://blog.csdn.net/weixin_32823555/article/details/112664220 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:平均值_Echarts图表配置显示最大、最小及平均值
下一篇:系列有什么区别_华为的Mate和P系列有什么区别?绝不止拍照这么简单!

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年03月30日 12时24分40秒