Java线程池(ThreadPoolExecutor)解读

什么是线程池,解决什么问题

线程池是一种池化技术,线程池解决了两个不同的问题:它们通常在执行大量异步任务时提供更好的性能,这是因为减少了每个任务的调用开销;它们提供了一种方法来限制和管理执行任务集合时消耗的资源,包括线程。每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。
其主要的作用线程的复用,减少了调用开销,管理消耗的资源。

使用线程池

public class PoolDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //执行Runnable
        pool.execute(() -> System.out.println(Thread.currentThread().getName()));
        //执行Callable
        Future<Integer> future = pool.submit(() -> {
            System.out.println(Thread.currentThread().getName());
            return 1024;
        });
        System.out.println(future.get());
    }
}

使用线程池执行任务时,只要将我们自己实现的线程任务通过**execute()submit()**方法执行即可。submit()最终调用的也是execute()方法

ThreadPoolExecutor源码解读

1.为何不推荐使用Executors去创建jdk自带的线程池

阿里巴巴Java开发手册
阿里巴巴《Java开发手册》为何不推荐使用自带的线程,他也给出了理由,但我们需要了解线程池中各参数的具体意义。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

说明:

参数含义
corePoolSize线程池核心线程数
maximumPoolSize线程池最大线程数,达到最大值后线程池不会再增加线程
keepAliveTime线程池中超过核心线程的空闲线程最大存活时间
unit上个参数的时间单位
workQueue阻塞队列
threadFactory线程工厂
handler线程池满时的拒绝策略

打开Excutors源码我们发现,
创建FixedThreadPool和SingleThreadExecutor时,使用的是LinkedBlockingQueue,他的容量是int最大值,可能会堆积大量请求,导致OOM。

public LinkedBlockingQueue(){
	this(Integer.MAX_VALUE);
}

pool2
创建CachedThreadPool时,线程池最大线程数为int最大值,虽然定义了存活时间,但同样可能创建大量线程,导致OOM。
pool2

2.ThreadPoolExecutor类中的属性与方法

了解线程池如何运行之前,我们要了解ThreadPoolExecutor类中一些重要属性与方法

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;// 29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;// 二进制 00011111 11111111 11111111 11111111 ,表示线程数容量

说明:

属性含义
ctl一个原子整数封装,前3位记录线程池的状态,后29位记录线程数(这个比较重要,经常用到)
COUNT_BITS有效线程占用的位数,定值29
CAPACITY有效线程的容量
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

说明:

属性含义二进制表示
RUNNING接受新任务并处理排队的任务11100000 00000000 00000000 00000000
SHUTDOWN不接受新任务,但处理排队的任务00000000 00000000 00000000 00000000
STOP不接受新任务,不处理排队的任务,并中断正在进行的任务00100000 00000000 00000000 00000000
TIDYING所有任务都已终止,workerCount为零,线程转换为TIDYING状态,将运行terminated()钩子方法01000000 00000000 00000000 00000000
TERMINATEDterminated()方法执行完成,线程池终止了01100000 00000000 00000000 00000000

我们可以发现,前三位不一样,对应不同状态,且5种状态对应的数值是递增的。他们的关系如下:
线程池生命周期

private final HashSet<Worker> workers = new HashSet<Worker>();

说明:

属性含义
workers包含池中所有工作线程的集。仅在获取mainLock时访问
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

说明:

方法作用
runStateOf(int c)通过ctl获取运行状态
int workerCountOf(int c)通过ctl获取工作线程数
ctlOf(int rs, int wc)通过运行状态与工作线程数计算出ctl

3.拒绝策略

jdk默认的实现拒绝策略有四种:
拒绝策略

3.1 AbortPolicy

AbortPolicy

3.2 CallerRunsPolicy

在这里插入图片描述

3.3 DiscardOldestPolicy

在这里插入图片描述

3.4 DiscardPolicy

DiscardPolicy

3.任务执行流程及源码分析

execute()执行流程如下,submit()最终调用的也是execute():
执行流程

execute()源码:

public void execute(Runnable command) {
		// 程序健壮性判断
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 尝试核心线程启动任务
        if (workerCountOf(c) < corePoolSize) {
        	// 这个方法代码较多,后面会介绍,大致就是任务成功加入工作线程,并成功启动会返回true
            if (addWorker(command, true))
                return;
            // addWorker()不是整个方法被锁住的,添加任务时,其他的线程可能捷足先登了,所以重新获取ctl,进行判断
            c = ctl.get();
        }
        // 核心线程已满,尝试加入队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
            	// 这里提前准备空任务线程,为处理队列中的任务做准备
                addWorker(null, false);
        }
        // 加入队列失败,尝试使用非核心线程启动,启动失败,执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

小结:通过execute()方法可以发现,流程与上面的执行流程是一致。所以后来的任务,可能会早于之前的任务执行。因为可能有任务在排队,而无法立即启动,而后来的却用非核心线程直接启动了。

addWorker()源码:

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // rs >= SHUTDOWN 如果线程池状态为RUNNING,则直接跳出判断执行后面的循环,尝试加入任务
            // ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()) 注意外层取反,所以里面只要有一个判断为false,则程序直接return false
            // rs == SHUTDOWN 如果状态不是SHUTDOWN,可能为STOP,TIDYING,TERMINATED,这些状态没有加入任务的必要了, 程序直接return false
            // firstTask == null SHUTDOWN状态,由前面得知,不接受新任务,所以任务不为空,程序直接return false
            // ! workQueue.isEmpty() 结合外层取反一起看,得到workQueue.isEmpty(),如果阻塞队列为空,则判断为true,程序直接return false
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // 超出容量无法继续创建
                // 超出核心线程数量,由execute()我们得知,会尝试用非核心线程去启动任务
                // 超出最大线程数量,则无法创建,返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 以cas方式,将ctl值加1,成功增加,则代表工作线程总数加1
                // 成功增加会跳出外层循环执行后面的程序
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 到达这里代表代表加1失败,则尝试重新增加。状态一致执行内循环,状态不一致从外循环重新开始
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

		// 到达这里代表compareAndIncrementWorkerCount(c)成功,则说明有位置供我们新的任务去启动
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	// 封装一个Worker,构造方法代码很少,但有个地方很重要,后面会讲
            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());
					// rs < SHUTDOWN 状态为RUNNING时为true
					// rs == SHUTDOWN && firstTask == null 状态为RUNNING为SHUTDOWN,需要创建空的任务线程,去处理阻塞队列任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 任务如果已经启动,则无法再次启动,直接抛异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 加入工作线程
                        workers.add(w);
                        int s = workers.size();
                        // 记录工作线程最大值
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                	// 这里后续会启动Worker的run()方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	// 启动失败,执行addWorkerFailed()
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

小结:addWorker()针对RUNNINGSHUTDOWN状态。如果为RUNNING,则创建线程去处理任务,如果为SHUTDOWN,则有可能创建空任务线程处理阻塞队列任务

内部类Worker源码:

Worker(Runnable firstTask) {
			// AQS相关,先不讨论
            setState(-1); 
            this.firstTask = firstTask;
            // 这里我们发现,new Thread时,传入的是Worker自己,那么后续启动线程时,则会调用Worker的run()方法
            this.thread = getThreadFactory().newThread(this);
        }

public void run() {
            runWorker(this);
        }

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	// 任务不为空时,则执行现有任务
        	// 任务为空时,则通过getTask()获取任务,后面介绍
        	// getTask() 体现线程的复用
            while (task != null || (task = getTask()) != null) {
            	// 进入循环代表有任务获取到
                w.lock();
                // 如果状态是STOP及以上的话,则主动中断当前线程
                // 如果状态是STOP以下的话,确保当前线程不是中断状态并二次检测线程池状态
                // 总结就是如果线程池STOP或者更高状态,我们就要放弃任务处理了
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    // 主动中断当前线程
                    wt.interrupt();
                try {
                	// 前置处理,未实现
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    	// 到这里我们的任务就真正的执行了
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                    	// 前置处理,未实现
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	// 主要是记录完成的任务总数以及从工作队列中移除此次任务
            processWorkerExit(w, completedAbruptly);
        }
    }

小结:这里主要为后续调用Worker的run()方法做准备,runWorker()里面才会真正启动任务。通过getTask()实现线程复用。

getTask()源码:

private Runnable getTask() {
		// 上次poll()是否超时
        boolean timedOut = false;

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
			// 线程池状态>=SHUTDOWN且(线程池状态>=STOP或 工作队列为空),则返回null
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);
            
            // allowCoreThreadTimeOut 核心线程是否允许被淘汰,默认为false,我们按false分析
            // wc > corePoolSize 工作线程数大于核心线程数时为true,否则为false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 工作线程数大于最大线程数时,减少ctl值,但是不返回对象
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            	// 工作线程数大于核心线程数时,执行workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
            	// 工作线程数小于等于核心线程数时,执行workQueue.take()
            	// 这两个方法都会取出队列的任务,不同的是poll会有一段时间的阻塞(前提定义了存活时间)
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

小结:getTask()会有3个retun,如果不是状态或者工作线程数量的问题,他会一直循环读取队列的任务,从而达到复用。

processWorkerExit()源码:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
		// 如果突然完成,则未调整workerCount,我们需要在这里进行调整
        if (completedAbruptly) 
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 如果设置了存活时间keepAliveTime,runWorker()的while()里就可能获取null,从而跳出循环,达到清楚空闲线程的功能
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

小结:这段代码是处理线程结束是的一些操作,可以达到清楚空闲线程的功能,但有些属性私有的,比如completedTaskCount ,外部无法查看,开发通过jvm看吗?

END

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值