java线程池原理剖析_java线程池原理剖析

java 线程池存在意义

很多刚接接触线程池的人会很纳闷,我明明可以用new Thread().start() 启动我想要的线程,那么为什么还多此一举,用线程池呢?

首先要明白程序最重要运行到一台机器上的,那么对于一台机器来说, 他的CPU,IO,网络,DB等资源是有限的,因此我们用的时候不能够随意支取,而是要有一个中介来统一接收请求,然后中介去帮忙对资源进行控制,调度,回收(数据库连接池也是一个道理,为了防止程序频繁无休止的创建Connection把数据库拖挂,所以用了连接池)。

线程对于一台机器来说是稀缺资源, 因此程序里面不能随意的new Thread,而应该通过创建一个全局Globa的 Thread Pool,然后大家如果想要申请使用Thread资源的话,统一给线程池下发任务,然后线程池里面有一个Task Queue缓存任务,然后线程池根据配置,结合当前机器的运行状况,开线程从Task QUeue中消费Task运行,这样防止把机器的线程资源耗尽

线程的生老病死都是要耗费性能的,所以最好不要频繁的创建回收Thread,浪费机器性能。因此用一个线程池,一直开着一定数量的线程,当有Task来了,就取线程消费Task Queue

解耦, 调用方只用创建Task,然后传给线程池,线程池缓存Task,然后线程池中的Thread消费Task Queue,这样达到Task创建和执行的解耦。

java如何创建线程池

比较常用的 用Executors的各种静态方法创建需要的线程池

ExecutorService cachedthreadPool = Executors.newCachedThreadPool();

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);

ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);

ExecutorService SingleThreadPool = Executors.newSingleThreadExecutor();

其实Executors的四种方法底层都是用 new ThreadPoolExecutor 来创建线程池的,因此为了搞清楚上面四种方法的区别,我们还是深入研究一下ThreadPoolExecutor的原理,为了研究ThreadPoolExector的原理我们先画图讲解JAVA 线程池的原理

Executors.newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

60L, TimeUnit.SECONDS,

new SynchronousQueue());

}

可以看到: newCachedThreadPool结果会创建一个最小0 -- Integer.MAX_VALUE个线程, 使用SynchronousQueue接受Task,如果队列满了,等待 60S,如果等待超时那么调用默认的defaultHandler也就是new AbortPolicy()

Executors.newFixedThreadPool()

/**

* Creates a thread pool that reuses a fixed number of threads

* operating off a shared unbounded queue. At any point, at most

* nThreads threads will be active processing tasks.

* If additional tasks are submitted when all threads are active,

* they will wait in the queue until a thread is available.

* If any thread terminates due to a failure during execution

* prior to shutdown, a new one will take its place if needed to

* execute subsequent tasks. The threads in the pool will exist

* until it is explicitly {@link ExecutorService#shutdown shutdown}.

*

* @param nThreads the number of threads in the pool

* @return the newly created thread pool

* @throws IllegalArgumentException if {@code nThreads <= 0}

*/

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue());

}

可以看到: newFixedThreadPool 结果会创建一个最小0 -- nThreads个线程, 使用 LinkedBlockingQueue 接受Task,如果队列满了,不等待,直接调用默认的defaultHandler也就是new AbortPolicy()

Executors.newScheduledThreadPool()

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {

return new DelegatedScheduledExecutorService

(new ScheduledThreadPoolExecutor(1));

}

其中的 ScheduledThreadPoolExecutor 如下

/**

* Creates a new {@code ScheduledThreadPoolExecutor} with the

* given core pool size.

*

* @param corePoolSize the number of threads to keep in the pool, even

* if they are idle, unless {@code allowCoreThreadTimeOut} is set

* @throws IllegalArgumentException if {@code corePoolSize < 0}

*/

public ScheduledThreadPoolExecutor(int corePoolSize){

super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,

new DelayedWorkQueue());

}

比较推荐的创建线程池方法是

因为Executors底层还是调用new ThreadPoolExecutor创建线程池,因此在平时使用的时候还是比较推荐直接使用ThreadPoolExecutor创建

java 线程池原理和处理流程

如果所示

threadPool.png

创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), new ArrayBlockingQueue(5), rejectedExecutionHandler)

【调用方提交Task】 调用 threadPoolExecutor.execute(task)或者threadPoolExecutor.submit(task)给线程池提交task

【threadPoolExecutor把task防区WorkQueue队列中】,等待线程池中Thread消费Task

【判断coreThreadPool线程额数是否达到最大】如果没有,且WorkQueue有堆积的Task那么,多开线程消费Task

【如果线程已经达到最大值,且WorkQueue已经满了,且又有新的Task来了,那么触发饱和策略】rejectedExecutionHandler

常见的饱和策略: 1) 直接丢弃; 2)调用发自己写rejectedExecutionHandler处理这些Task; 3)丢弃队列中的最近任务

合理配置线程池内core thread个数

线程池中的线程个数不是越大越好,要根据任务分类来的。

IO密集型任务: 线程个数增大,可以增加系统的吞吐。比如可以配置线程个数为 CPU线程数的2倍

计算密集型任务: 为了避免频繁切换CPU线程上下文,所以线程个数应该跟根据CPU核数配置,CPU线程数的0.8倍。比如CPU是2核4线程的,那么计算密集型的话,可以配置线程最大个数为 4 * 0.8 = 3个

混合型任务: 可以结合上面两类综合考虑配置

一些外面文章说的不恰当的论述

1. 线程池不用的时候为什么要调用shutDown() ?

网上一贯回答: 我们是为了不让线程池再新加task。

个人理解

为了解答这个问题,直接看源码最直接,省的弯弯绕绕的。

public void shutdown(){

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

checkShutdownAccess();

advanceRunState(SHUTDOWN);

interruptIdleWorkers();

onShutdown(); // hook for ScheduledThreadPoolExecutor

} finally {

mainLock.unlock();

}

//核心地方在这里

tryTerminate();

}//这个会一直轮训, 直到所有task执行完,然后销毁线程池的线程,关闭线程池

final void tryTerminate() {

for (;;) {

int c = ctl.get();

if (isRunning(c) ||

runStateAtLeast(c, TIDYING) ||

(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))

return;

if (workerCountOf(c) != 0) { // Eligible to terminate

interruptIdleWorkers(ONLY_ONE);

return;

}

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {

try {

terminated();

} finally {

ctl.set(ctlOf(TERMINATED, 0));

termination.signalAll();

}

return;

}

} finally {

mainLock.unlock();

}

// else retry on failed CAS

}

}

综上:

主要目的是当线程池不用的时候, 销毁线程池维持的线程,关闭线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值