摘要:从整体上意识下线程池中最外围的类之一——ThreadPoolExecutor,对于 ThreadPoolExecutor 的底层原理和源码实现,以及线程池中的其余技术细节的底层原理和源码实现。
本文分享自华为云社区《高并发之——不得不说的线程池与 ThreadPoolExecutor 类浅析》,作者:冰 河。
既然 Java 中反对以多线程的形式来执行相应的工作,但为什么在 JDK1.5 中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,必定没害处,哈哈哈。。。
说起中的线程池技术,在很多框架和异步解决中间件中都有波及,而且性能禁受起了短暂的考验。能够这样说,Java 的线程池技术是 Java 最外围的技术之一,在 Java 的高并发畛域中,Java 的线程池技术是一个永远绕不开的话题。既然 Java 的线程池技术这么重要(怎么能说是这么重要呢?那是相当的重要,那家伙老重要了,哈哈哈),那么,本文咱们就来简略的说下线程池与 ThreadPoolExecutor 类。
一、Thread 间接创立线程的弊病
(1)每次 new Thread 新建对象,性能差。
(2)线程不足对立治理,可能无限度的新建线程,相互竞争,有可能占用过多系统资源导致死机或 OOM。
(3)短少更多的性能,如更多执行、定期执行、线程中断。
(4)其余弊病,大家自行脑补,多动脑,没害处,哈哈。
二、线程池的益处
(1)重用存在的线程,缩小对象创立、沦亡的开销,性能佳。
(2)能够无效管制最大并发线程数,进步系统资源利用率,同时能够防止过多资源竞争,防止阻塞。
(3)提供定时执行、定期执行、单线程、并发数管制等性能。
(4)提供反对线程池监控的办法,可对线程池的资源进行实时监控。
(5)其余益处,大家自行脑补,多动脑,没害处,哈哈。
三、线程池
1. 线程池类构造关系
线程池中的一些接口和类的构造关系如下图所示。
后文会死磕这些接口和类的底层原理和源码。
2. 创立线程池罕用的类——Executors
- Executors.newCachedThreadPool:创立一个可缓存的线程池,如果线程池的大小超过了须要,能够灵便回收闲暇线程,如果没有可回收线程,则新建线程
- Executors.newFixedThreadPool:创立一个定长的线程池,能够控制线程的最大并发数,超出的线程会在队列中期待
- Executors.newScheduledThreadPool:创立一个定长的线程池,反对定时、周期性的工作执行
- Executors.newSingleThreadExecutor: 创立一个单线程化的线程池,应用一个惟一的工作线程执行工作,保障所有工作依照指定程序(先入先出或者优先级)执行
- Executors.newSingleThreadScheduledExecutor: 创立一个单线程化的线程池,反对定时、周期性的工作执行
- Executors.newWorkStealingPool:创立一个具备并行级别的 work-stealing 线程池
3. 线程池实例的几种状态
- Running: 运行状态,能接管新提交的工作,并且也能解决阻塞队列中的工作
- Shutdown: 敞开状态,不能再接管新提交的工作,然而能够解决阻塞队列中曾经保留的工作,当线程池处于 Running 状态时,调用 shutdown()办法会使线程池进入该状态
- Stop: 不能接管新工作,也不能解决阻塞队列中曾经保留的工作,会中断正在解决工作的线程,如果线程池处于 Running 或 Shutdown 状态,调用 shutdownNow()办法,会使线程池进入该状态
- Tidying: 如果所有的工作都曾经终止,无效线程数为 0(阻塞队列为空,线程池中的工作线程数量为 0),线程池就会进入该状态。
- Terminated: 处于 Tidying 状态的线程池调用 terminated()办法,会应用线程池进入该状态
留神:不须要对线程池的状态做非凡的解决,线程池的状态是线程池外部依据办法自行定义和解决的。
4. 合理配置线程的一些倡议
(1)CPU 密集型工作,就须要尽量压迫 CPU,参考值能够设置为 NCPU+1(CPU 的数量加 1)。
(2)IO 密集型工作,参考值能够设置为 2 *NCPU(CPU 数量乘以 2)
四、线程池最外围的类之一——ThreadPoolExecutor
1. 构造方法
ThreadPoolExecutor 参数最多的构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler rejectHandler)
其余的构造方法都是调用的这个构造方法来实例化对象,能够说,咱们间接剖析这个办法之后,其余的构造方法咱们也明确是怎么回事了!接下来,就对此构造方法进行具体的剖析。
留神:为了更加深刻的剖析 ThreadPoolExecutor 类的构造方法,会适当调整参数的程序进行解析,以便于大家更能深刻的了解 ThreadPoolExecutor 构造方法中每个参数的作用。
上述构造方法接管如下参数进行初始化:
(1)corePoolSize:外围线程数量。
(2)maximumPoolSize:最大线程数。
(3)workQueue:阻塞队列,存储期待执行的工作,很重要,会对线程池运行过程产生重大影响。
其中,上述三个参数的关系如下所示:
- 如果运行的线程数小于 corePoolSize,间接创立新线程解决工作,即便线程池中的其余线程是闲暇的。
- 如果运行的线程数大于等于 corePoolSize,并且小于 maximumPoolSize,此时,只有当 workQueue 满时,才会创立新的线程解决工作。
- 如果设置的 corePoolSize 与 maximumPoolSize 雷同,那么创立的线程池大小是固定的,此时,如果有新工作提交,并且 workQueue 没有满时,就把申请放入到 workQueue 中,期待闲暇的线程,从 workQueue 中取出工作进行解决。
- 如果运行的线程数量大于 maximumPoolSize,同时,workQueue 曾经满了,会通过回绝策略参数 rejectHandler 来指定解决策略。
根据上述三个参数的配置,线程池会对工作进行如下解决形式:
当提交一个新的工作到线程池时,线程池会依据以后线程池中正在运行的线程数量来决定该工作的解决形式。解决形式总共有三种:间接切换、应用有限队列、应用有界队列。
- 间接切换罕用的队列就是 SynchronousQueue。
- 应用有限队列就是应用基于链表的队列,比方:LinkedBlockingQueue,如果应用这种形式,线程池中创立的最大线程数就是 corePoolSize,此时 maximumPoolSize 不会起作用。当线程池中所有的外围线程都是运行状态时,提交新工作,就会放入期待队列中。
- 应用有界队列应用的是 ArrayBlockingQueue,应用这种形式能够将线程池的最大线程数量限度为 maximumPoolSize,能够升高资源的耗费。然而,这种形式使得线程池对线程的调度更艰难,因为线程池和队列的容量都是无限的了。
依据下面三个参数,咱们能够简略得出如何升高系统资源耗费的一些措施:
- 如果想升高系统资源的耗费,包含 CPU 使用率,操作系统资源的耗费,上下文环境切换的开销等,能够设置一个较大的队列容量和较小的线程池容量。这样,会升高线程解决工作的吞吐量。
- 如果提交的工作常常产生阻塞,能够思考调用设置最大线程数的办法,从新设置线程池最大线程数。如果队列的容量设置的较小,通常须要将线程池的容量设置的大一些,这样,CPU 的使用率会高些。如果线程池的容量设置的过大,并发量就会减少,则须要思考线程调度的问题,反而可能会升高解决工作的吞吐量。
接下来,咱们持续看 ThreadPoolExecutor 的构造方法的参数。
(4)keepAliveTime:线程没有工作执行时最多放弃多久工夫终止
当线程池中的线程数量大于 corePoolSize 时,如果此时没有新的工作提交,外围线程外的线程不会立刻销毁,须要期待,直到期待的工夫超过了 keepAliveTime 就会终止。
(5)unit:keepAliveTime 的工夫单位
(6)threadFactory:线程工厂,用来创立线程
默认会提供一个默认的工厂来创立线程,当应用默认的工厂来创立线程时,会使新创建的线程具备雷同的优先级,并且是非守护的线程,同时也设置了线程的名称
(7)rejectHandler:回绝解决工作时的策略
如果 workQueue 阻塞队列满了,并且没有闲暇的线程池,此时,持续提交工作,须要采取一种策略来解决这个工作。
线程池总共提供了四种策略:
- 间接抛出异样,这也是默认的策略。实现类为 AbortPolicy。
- 用调用者所在的线程来执行工作。实现类为 CallerRunsPolicy。
- 抛弃队列中最靠前的工作并执行当前任务。实现类为 DiscardOldestPolicy。
- 间接抛弃当前任务。实现类为 DiscardPolicy。
2.ThreadPoolExecutor 提供的启动和进行工作的办法
(1)execute(): 提交工作,交给线程池执行
(2)submit(): 提交工作,可能返回执行后果 execute+Future
(3)shutdown(): 敞开线程池,期待工作都执行完
(4)shutdownNow(): 立刻敞开线程池,不期待工作执行完
3.ThreadPoolExecutor 提供的实用于监控的办法
(1)getTaskCount():线程池已执行和未执行的工作总数
(2)getCompletedTaskCount():已实现的工作数量
(3)getPoolSize():线程池以后的线程数量
(4)getCorePoolSize():线程池外围线程数
(5)getActiveCount(): 以后线程池中正在执行工作的线程数量
点击关注,第一工夫理解华为云陈腐技术~