线程池调度ThreadPoolExecutor详解

        先来看个Demo:

ThreadPoolExecutor executor;
private void testThreadPool() {
    executor = new ThreadPoolExecutor(3,10,1000,
            TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(20),new ThreadPoolExecutor.CallerRunsPolicy());
    for (int i = 0; i < 50; i++) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程名 = "+Thread.currentThread().getName()+"    "+executor.getPoolSize());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

打印log:

线程名 = pool-1-thread-3    10
线程名 = pool-1-thread-2    10
线程名 = pool-1-thread-1    10
线程名 = main    10
线程名 = pool-1-thread-6    10
线程名 = pool-1-thread-10    10
线程名 = pool-1-thread-9    10
线程名 = pool-1-thread-4    10
线程名 = pool-1-thread-5    10
线程名 = pool-1-thread-8    10
线程名 = pool-1-thread-7    10

看到这,肯能会有人有疑问,为什么会有在main线程中执行的呢?接下来就来看看ThreadPoolExecutor的实现。

构造方法参数介绍:

        ThreadPoolExecutor构造方法的参数有以下几个,分别是corePoolSize,maxinumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler,这几个参数的作用是:

        1、corePoolSize:核心线程数量,即使是空闲的时候也会保持的线程数量,除非设置了allowOCreThreadTimeOut为true才会在空闲的时候回收掉;

        2、maxinumPoolSize:允许在线程池中的最大线程数量,只有在线程数量大于corePoolSize+workQueue.size()时才会在新建corePoolSize之外的线程;

        3、keepAliveTime:当线程数量超过核心线程数量时,多出来的线程会在闲置这个时间后进行回收;

        4、unit:keepTimeAlive的时间单位;

        5:workQueue:存放还没执行的任务队列,Java提供了几种可供我们自行选择的集合,比如:LinkedBlockQueue、ArrayBlockQueue,SynchronousQueue;

        6、threadFactory:创建线程的工厂类;

扫描二维码关注公众号,回复: 1519443 查看本文章

        7、handler:任务执行失败或添加的任务超过了队列的容量。

线程池状态介绍:

*   RUNNING:  Accept new tasks and process queued tasks
*   SHUTDOWN: Don't accept new tasks, but process queued tasks
*   STOP:     Don't accept new tasks, don't process queued tasks,
*             and interrupt in-progress tasks
*   TIDYING:  All tasks have terminated, workerCount is zero,
*             the thread transitioning to state TIDYING
*             will run the terminated() hook method
*   TERMINATED: terminated() has completed

* RUNNING -> SHUTDOWN
*    On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
*    On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
*    When both queue and pool are empty
* STOP -> TIDYING
*    When pool is empty
* TIDYING -> TERMINATED
*    When the terminated() hook method has completed

        上面是文档中的一些介绍,英语水平有限,翻译的不好还请见谅:

1、RUNNING:接收新的任务和处理已经进入队列里的任务;

2、SHUTDOWN:不接收新的任务但会处理已经在队列里的任务;

3、STOP:不接收新的任务和处理已经在队列里的任务,而且还会打断正在执行的任务;

4、TIDYING:所有的任务已经终止,workCount为0,线程状态转化为TIDYING然后执行terminated()方法;

5、TERMINATED:terminated()方法执行完成。

        状态转换:

1、RUNNING--->SHUTDOWN:调用了shutDown()方法或是在销毁;

2、RUNNING--->STOP:调用了shutDownNow()方法;

3、SHUTDOWN--->TIDYING:当队列和线程次为空;

4、STOP--->TIDYING:当线程池为空;

5、TIDYING--->TERMINATED:当terminated()方法执行完成。

线程池状态和数量存放:    

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
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;

// Packing and unpacking ctl
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; }

线程池状态和数量就是存放在ctl中的,ctl是线程安全的,ctl是32位bit的一个数,其中高三位是用来存放线程池状态的,其余的是用来存放数量的。

线程开始执行的方法:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    //上面的三点主要是讲解了下面的执行的流程和作用
    int c = ctl.get();
    //如果线程池中的线程小于核心线程数,
    // 尝试开启一个新的线程以传进来的任务作为他的第一个任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        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()方法,
        reject(command);
}

对于这个方法讲解,源码中的英文已经给我们讲解的很清楚了,主要分为三点:

        1、先判断线程池中的线程是否低于核心线程,如果低于,那就创建一个新的线程来放置这个任务并返回;

        2、如果大于核心线程,正常的情况都是这个任务加入到队列中去,然后需要在此检查是否需要新建一个线程(因为可能存在一个线程在上次检查完之后被回收了)或者因为线程池停止了。所以需要再次检查状态,如果不在RUNNING状态并且能够成功移除任务的话,那么调用reject方法,否则就调用addWorker(null,false)方法。

        3、如何任务不能加入队列,就会尝试创建一个新的线程来执行这个任务,如果创建线程失败了,那就会reject()。

        通过对上面的源码的分析,我们可以看到其中主要涉及到两个方法:addWorker()和reject():

        我们先来看一下比较简单的方法reject():

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

这里就是调用了构造方法中传进来的handler对象,如果不传那就会使用默认的,Java中为我们准备了四个这样的对象,下面就来看看:

//在那个线程中调用的就在那个线程执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}
//直接跑异常
public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
    }
}
//什么处理都不做
public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}
//去除队列头后在将任务加入到队列尾
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

默认使用的就是直接抛异常了,如果上面几种方法都不符合你得处理需求,那你可以自定义自己的处理方式。在这里我们看到了callerRunsPolicy这个对象,这也是最开始demo使用的处理方式,直接是在添加任务的线程执行run()方法。

        接下来就还有addWork()这个方法了:

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        //得到运行状态
        int rs = runStateOf(c);
        // 检查运行状态
        if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                        firstTask == null &&
                        ! workQueue.isEmpty()))
            return false;

        for (;;) {
            //得到线程池中的数量
            int wc = workerCountOf(c);
            //检查线程池中数量是否大于容量,或者核心线程数和最大线程数
            if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //线程池数量+1成功,跳出循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //否者重新读取ctl
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //以firstTask作为第一个任务创建worker(构造方法中会创建线程)并执行
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //workers是一个HashSet对象,里面存放的是工作线程
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                //开始执行任务
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            //开启线程失败调用这个方法
            addWorkerFailed(w);
    }
    return workerStarted;
}

这里主要可以分为两步,一是检查线程池的状态,二是线程池状态ok就创建线程并开启。

        接下来我们来看看是如何创建线程的,这里创建线程主要用到的是Worker这个类,我们来看看Worker这个类的结构:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;
    Runnable firstTask;
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    
    public void run() {
        runWorker(this);
    }
}

这里只列出了一部分Worker的代码,Worker本身是实现了Runnable接口的,当我们创建线程的时候把这个对象传进去,那么当我们调用Thread.start()方法是,调用到的就是Worker的这个run()方法了。再来看runWorker():

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()就是执行去任务的动作,
        // 当任务为null时才会才会退出,这也就意味着这个线程执行完成
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
                wt.interrupt();
            try {
                //这个方法是一个空方法,当你想在任务执行前做点什么,重写下这个方法就ok了
                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);
    }
}

我们知道,一个线程的生命周期其实就是run() 方法了,当run()执行完成意味着线程也就结束了,从上面的while()循环中我们知道,这里线程的生命周期其实就是由getTask()在决定了:

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //是否允许线程超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            //根据timed这个标示决定是一直阻塞还是到达时间后退出这个线程(线程结束),
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

getTask()其实一直去队列里取任务,当任务队列里没有任务时,有两种方法去队列拿任务,这两个方法都是阻塞方法,poll(keepAliveTime,unit):这个方法是在到时间后会返回null,而take() 方法则会一直阻塞。

        到这,线程池的整个流程就分析分差不多了,这里在总结一下:

执行流程:

1. 如果当前线程数小于corePoolSize,那么创建线程并保存到线程集合中;

2. 如果当前线程数大于等于corePoolSize,那么将任务加入队列;如果成功加入,那么就等待线程的getTask获取到任务再去执行,如果线程池中的线程数大于核心线程数,那么getTask()在空闲的时间大于keepAliveTime时会返回null,线程执行结束。

3. 如果第2步中加入队列失败,并且当前线程数小于maximumPoolSize,会尝试开启线程,如果大于等于maximumPoolSize,那么创建线程失败,会交由RejectExecutionHandler的rejectExecution()处理。

使用:

对于线程池的使用,Android中建议使用AsyncTask中的线程池

public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
看到没有,public还是static的,那我们直接拿来用就可以了,这里的核心线程数是根据CPU来确定的,免去了我们自己去创建线程池,不过这里有需要注意的地方,那就是这个线程池中的队列容量是有限制的,为128。




猜你喜欢

转载自blog.csdn.net/tangedegushi/article/details/80053520