java并发-ThreadPoolExecutor源码阅读笔记

       java并发包中的任务调度框架Executor的实现类ThreadPoolExecutor的源码阅读。

   线程扩容机制 

       ThreadPoolExecutor提供了某些Executor的基本实现,是一个灵活的、稳定的、支持定制的线程池。首先,类结构图如下:

java并发-ThreadPoolExecutor源码阅读笔记_第1张图片

    

       线程池维护着三个容量变量poolSize、corePoolSize、maxPoolSize,以及一个空闲时间keepAliveTime。这四个因素共同决定着线程的创建和销毁。通过调节线程池的基本大小和存活时间,可以帮助线程池回收空闲线程占有的资源。任务调度流程源码:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
            else if (!addIfUnderMaximumPoolSize(command))
                reject(command); // is shutdown or saturated
        }
    }

      分析该方法的注释,可以了解任务的调度流程。当活动线程的数量poolSize小于corePoolSize时,每次收到一个新任务时,都新创建一个线程去执行该任务;如果活动线程规模达到corePoolSize时,新提交的任务开始在工作队列中排队直到队列塞满;当队列塞满时,线程池则动态扩展工作线程数量,直到maxPoolSize;当活动线程数达到maxPoolSize且排队队列已满,仍然有新提交任务时,线程池则执行饱和策略调用RejectedExecutionHandler的rejectExecution方法。

      动态扩容活动线程的两个方法addInUnderCorePoolSize和addIfUderMaxinumPoolSize方法操作类似,都是新建工作线程去执行新提交的任务;而ensureQueuedTaskHandler方法则是响应线程池的关闭操作,保证线程池被关闭时,已排队的任务要么被执行要么被抛弃(即不再接受新任务)。源码如下:

 private void ensureQueuedTaskHandled(Runnable command) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        boolean reject = false;
        Thread t = null;
        try {
            int state = runState;
            if (state != RUNNING && workQueue.remove(command))
                reject = true;
            else if (state < STOP &&
                     poolSize < Math.max(corePoolSize, 1) &&
                     !workQueue.isEmpty())
                t = addThread(null);
        } finally {
            mainLock.unlock();
        }
        if (reject)
            reject(command);
        else if (t != null)
            t.start();
    }

        监测到线程池关闭操作时,如果刚提交的排队任务还没有被执行,则直接抛弃该任务;否则就新建一个工作线程,其初始任务为空,由Worker区轮询排队队列执行已提交的任务。看源码的收获:代码的严谨,finally中执行必要的善后处理;Worker,工作者线程的执行流程,它时Runnable的实现类,run是一个轮询排队队列并执行任务的过程。还有就是对线程池中断操作的响应,保证中断后线程池不再接受新任务。

   线程池提交任务的流程

      线程池类ThreadPoolExecutor中新建的线程的参数是Worker,Workder是它的一个内部私有类,Worker的run方法中会轮询线程池的排队队列这个属性,不断获取新的任务继续执行。线程池和工作线程的详细类图如下:

      线程池扩容过程就是根据上述扩容几种创建新线程的过程:
    private Thread addThread(Runnable firstTask) {
        Worker w = new Worker(firstTask);
        Thread t = threadFactory.newThread(w);
        if (t != null) {
            w.thread = t;
            workers.add(w);
            int nt = ++poolSize;
            if (nt > largestPoolSize)
                largestPoolSize = nt;
        }
        return t;
    }
     ThreadPoolExecutor有一个ReentrantLock类型的属性,在execute执行过程中,需要获取这个锁,才能提交任务。也就是说线程池每次只允许有一个线程能够提交任务。这点可以理解。但是,Worker类中也有一个锁,在runTask每次执行任务时都要获取这个锁。为什么工作线程也有一个锁呢?这点还需要仔细想想。仔细分析一下,Worker类的runTask方法需要调用外部类的getTask方法,这个方法会遍历所有的Worker类,调用其interruptIfIdle()方法,代码如下。这就使得Worker类不仅被当前工作线程调用,而且还会被其他工作线程进行空闲检查,这也是runLock的作用,保护工作线程执行任务时不会被其他工作线程干扰。
        void interruptIfIdle() {
            final ReentrantLock runLock = this.runLock;
            if (runLock.tryLock()) {
                try {
		    if (thread != Thread.currentThread())
			thread.interrupt();
                } finally {
                    runLock.unlock();
                }
            }
        }
     那么问题来了:工作线程怎么会出现空闲的情况呢?从代码看,工作线程会循环遍历任务队列,如果取不到任务,就一直处于循环状态,也就是所谓的空闲。直到线程池对象被关闭,工作线程才会结束。
        public void run() {
            try {
                Runnable task = firstTask;
                firstTask = null;
                while (task != null || (task = getTask()) != null) {
                    runTask(task);
                    task = null;
                }
            } finally {
                workerDone(this);
            }

你可能感兴趣的:(java,线程池)