深入理解Java线程池(1):ThreadPoolExecutor整体流程梳理,创建worker相关方法

线程池作为一个线程的容器,主要的作用就是防止频繁创建线程,节省时间资源和cpu资源。虽然一定程度上占用了内存,但实际情况下利远远大于弊。

构造方法

public ThreadPoolExecutor(
	int corePoolSize,					//核心线程数量
	int maximumPoolSize,				//最大线程数量
	long keepAliveTime,					//最大存活时间
	TimeUnit unit,						//时间单位
	BlockingQueue<Runnable> workQueue,	//线程队列
	ThreadFactory threadFactory,		//线程工厂
	RejectedExecutionHandler handler)	//超过最大线程后,处理方式

上边的是最核心的构造方法,线程池的运作方式其实从构造方法就可以略知一二。我先简述一下线程池的运作方法,以及设置的参数的作用。

  • 创建线程池后,其实线程池中线程数量为0;
  • 当有一个任务被提交后,会创建一个worker线程,worker会被放入线程池中并开始执行这个任务,任务完成后会等待我们设置的任务队列(workQueue)中的任务。
  • 这样不停的提交任务,worker的数量也会不断增多,直到增大到核心线程数量(corePoolSize),此时不会再增加线程池中的worker线程,多出的任务会放入队列中,等到核心线程池中的worker空闲下来再执行放入队列中的任务。如果队列是无限队列可能会出现创建过多线程撑爆内存的现象。
  • 当队列满员后,会继续往线程池中增加worker,直到达到线程池最大线程数量(maximumPoolSize)。此时,会根据设置的RejectedExecutionHandler实现类(handler)执行不同的拒绝策略,例如:抛出异常,或者使用当前线程执行。若最大线程数量过大同样会出现worker数量过多撑爆内存的现象。
  • 当线程池的worker数量超过核心线程数量时,有worker的任务执行完毕后获取队列中的任务超过指定时间(keepAliveTime和unit确定的时间),这个worker就会被消灭。
  • 线程工厂(threadFactory)就是创建线程的地方,可使用Guava的ThreadFactoryBuilder().setNameFormat(“demo-thread-%d”).build()创建threadFactory。也可以使用自带的DefaultThreadFactory。

以上就是线程池运行的大概流程。队列数量过大或者最大线程数量过大,会让服务器挂掉,由于一个服务干爆整个服务器实属不智,应当谨慎使用Executors中的创建方式。

全局变量

下边先看看ThreadPoolExecutor中的全局变量,构造方法中涉及的就不在赘述

//使用了线程安全的AtomicInteger类,保证了ctl的不会因多线程而出现问题
//ctl变量值的前 29 位表示工作线程数量 workerCount, 剩余高位来表示线程池状态runState。
//初始化时默认是running状态,worker数量为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//running状态;接受新任务,并处理队列任务,是正常执行状态
private static final int RUNNING    = -1 << COUNT_BITS;	
//shutdown状态;不接受新任务,但会处理队列任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;	
//stop状态;不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务
private static final int STOP       =  1 << COUNT_BITS;	
//tidying状态;所有的任务已结束,workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法(此方法为空)
private static final int TIDYING    =  2 << COUNT_BITS;	
//terminated状态;terminated()方法已经完成后变更成此状态。
private static final int TERMINATED =  3 << COUNT_BITS;	

//储存worker的容器,不是线程安全,所以其操作通常需要加锁
private final HashSet<Worker> workers = new HashSet<Worker>();
//全局锁,会保证关键操作不会因并发而混乱,如对workers的操作。
private final ReentrantLock mainLock = new ReentrantLock();		

其他全局变量可能并非关键变量,如果遇到会在代码里说明。
下面开始对源码进行分析

execute方法

这是线程池的最核心方法,加入Runnable任务,使用线程池中的worker执行。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
	//计算 workerCount 和 runState 时通过掩码计算。
    int c = ctl.get();
    //若worker数量小于核心线程池中线程数,增加worker,若成功则直接返回。失败继续运行
    if (workerCountOf(c) < corePoolSize) {				
        if (addWorker(command, true))					
            return;
        c = ctl.get();									
    }
    //判断若是running状态,尝试向队列中添加任务。运行到此处,核心线程池已满,尝试向队列中添加任务
    if (isRunning(c) && workQueue.offer(command)) {	
        int recheck = ctl.get();
        //由于没有加锁,需要重新判断现在是否处于运行状态,若不是需要移除队列中任务,移除后执行插入失败操作reject
        if (! isRunning(recheck) && remove(command))	
            reject(command);
		//如果正常需要查看工作的worker数量,若为0,则加入一个空闲Worker。
        else if (workerCountOf(recheck) == 0)			
            addWorker(null, false);
    }
	//运行到此处说明,核心线程已经饱和,阻塞队列已经满员,尝试再次增加worker环节队列压力
    else if (!addWorker(command, false))reject(command);
}

增加空任务worker的逻辑比较奇怪,可能是状态突然变更为shutdown,队列中的任务已经被加入,shutdown操作意外杀死了所有worker线程,不得不执行这个操作。某种程度上保证了不会出现插入任务却没有worker的情况。根据addWorker方法中判断是否添加worker的逻辑推断得出,尚未完全明白为何会发生这种情况。

addWorker

addWorker方法会尝试向线程池中添加线程。
调用此方法的方法往往不会对线程池状态做出详细判断,所以addWorker方法需要对当前状态做出明确判断,根据不同状态执行不同操作。
入参firstTask表示worker的第一个任务,可能为null,表示只增加worker,不设置初始任务,worker执行的任务从队列中获取;入参core表示是否加入核心线程池

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
/* 判断何时不添加worker直接返回。
 * 若线程池状态非running时,一下三个条件都满足才继续运行后边的代码,否则其他非running状态都是值节返回失败
 * 1 线程池是shutdown状态 2 创建的worker的任务为空 3 任务队列中有任务未完成
 * 由此可以推断出execute加入无任务worker的原因。
 */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
//判断线程池数量,大于线程池能储存最大数量,直接返回
//判断入参core,如果true线程池数量小于核心线程数,如果false小于设置最大线程数
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
//cas增加worker数量,成功跳出,跳过所有循环;cas保证了并发增加worker不会发生异常
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get(); 
//运行到此处说明ctl改变,若是state改变则跳出到大循环,从新判断;若是worker数量改变则在小循环中继续运行
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
//worker是否启动
    boolean workerStarted = false;
//woker是否被添加入worker容器
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
//加上锁机制保证不会发生冲突
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
//最后的判断,和mainLock锁一起保证不会发生并发冲突
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
//在这里开始真正增加worker。
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
//启动worker线程,worker的run方法会调用runWorker方法,不停的领取任务
                t.start();					
                workerStarted = true;
            }
        }
    } finally {
//worker启动失败,此时调用addWorkerFailed方法。注意,此时ctl中worker数量已经+1,但worker容器中不一定添加。
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorkerFailed

这个方法被调用时,worker因不明原因启动或添加失败。线程池总worker数量加一了。所以需要尝试把worker容器中的启动失败的worker剔除,worker数量减一。

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();		
        tryTerminate();			//如有需要尝试关闭线程池
    } finally {
        mainLock.unlock();
    }
}

附上另外两篇ThreadPoolExecutor文章:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83615323
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83717842

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值