Java线程池工作流程

2,354 阅读5分钟

ThreadPoolExecutor构造函数

首先看下线程池构造函数中的几个配置项含义

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

corePoolSize:核心线程数

maximumPoolSize:最大线程数

keepAliveTime:总线程数大于核心线程数时,空闲线程允许存活的时间(这里并不严谨,后面会讲)

unitkeepAliveTime的单位

workQueue:任务队列

threadFactory:用来创建线程的工厂

handler:拒绝策略

execute方法

execute()是线程池执行任务的入口,submit()实际上也是调用了execute()

    public void execute(Runnable command) {
        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(command);
    }

其中ctl用于存储当前线程池状态和线程总量,addWorker(Runnable firstTask, boolean core)用于创建线程

execute()应对三种情况:

  1. 工作线程 < corePoolSize

    新建一个核心线程来执行任务

  2. 工作线程 >= corePoolSizeworkQueue没满

    将任务添加到workQueue,如果当前池中没有线程就新建一个

  3. 工作线程 >= corePoolSizeworkQueue已满

    尝试创建线程来执行任务

中间出问题了会采取拒绝策略reject(command)

看完这个方法就能知道系统提供的几种线程池的特点了

  1. FixedThreadPooler

    new LinkedBlockingQueue<Runnable>()创建出的是一个容量为Integer.MAX_VALUE的队列

    	public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    

    特点是workQueue的大小被设置成了Integer.MAX_VALUE,根据上面的三种情况,当线程数达到corePoolSize时,所有的任务都会被插入到workQueue中,最多同时执行corePoolSize个任务。这样maximumPoolSize也就没有意义了,所以被设置成了与corePoolSize一样的值。

  2. SingleThreadExecutor

        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    

    其实就是Executors.newFixedThreadPool(1),特点跟上面那个一样,只是corePoolSize被设置成了1,每次只能有一个任务在执行

  3. CachedThreadPool

        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    

    特点是corePoolSize为0,maximumPoolSizeInteger.MAX_VALUESynchronousQueue是个没有容量的队列,如果当前没有空闲线程正在poll或者takeoffer就会返回false,所以任务进来时,只要当前没有空闲线程,CachedThreadPool就会新建一个线程来执行任务。也就是说,所有任务都会立刻开始执行。

addWorker方法

    private boolean addWorker(Runnable firstTask, boolean core) {
        boolean workerStarted = false;
        Worker w = null;
        w = new Worker(firstTask);
        final Thread t = w.thread;
        workers.add(w);
        t.start();
        workerStarted = true;
        return workerStarted;
    }

addWorker方法比较长,但是去掉各种状态检测,实际上就干了两件事

  1. 创建Worker并添加至workers
  2. 启动Worker中的Thread

关于它是如何执行任务的,还得看一下Worker这个类(WorkerThreadPoolExecutor的内部类)

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {
        final Thread thread;
        Runnable firstTask;
        Worker(Runnable firstTask) {
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

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

Worker实现了Runnable接口,创建线程时把自己作为参数传了进去,run()方法中调用了ThreadPoolExecutorrunWorker方法

    final void runWorker(Worker w) {
        Runnable task = w.firstTask;
        w.firstTask = null;
        while (task != null || (task = getTask()) != null) {
            task.run();
            task = null;
        }
    }

简化一下就是这样子的,如果创建Worker传入的firstTask不为null,那么首先会执行firstTask,执行完毕后,该线程会尝试通过getTask方法继续获取任务来执行,当getTask返回null时,线程就会运行结束

getTask方法

    private Runnable getTask() {
        boolean timedOut = false;
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            if ((rs >= SHUTDOWN && workQueue.isEmpty()) || rs >= STOP ) {
                return null;
            }
            int wc = workerCountOf(c);
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

先不管别的,直接看从workQueue中获取任务的代码

    Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
    if (r != null)
    	return r;
    timedOut = true;

根据timed字段来决定是使用poll方法还是take方法,take方法会一直阻塞直到获取到任务,而poll如果不能在规定时间内获取到任务就会超时返回nulltimeOut会被标记为ture,下次循环时会返回null让正在通过getTask方法获取任务的线程结束

timed是由allowCoreThreadTimeOut和工作线程数决定的,当allowCoreThreadTimeOuttrue或者工作线程数大于corePoolSize时,timed就会被标记为ture,前者表示允许核心线程超时,后者表示当前工作线程大于核心线程数,所以当allowCoreThreadTimeOutfales时,线程池的大小达到或超过corePoolSize后,线程池总会留着corePoolSize个线程不会去关闭(工作线程小于等于corePoolSize时,timedfalse,会通过take方法从workQueue获取任务,所以不会超时关闭)

再看下其他代码,会让线程停止的几种情况:

  1. 线程池已经SHUTDOWNworkQueue为空
  2. 线程池已经STOP
  3. 工作线程数大于maximumPoolSize
  4. 线程获取任务超时

总结

  1. 构造函数中的三个重要的配置项

    corePoolSize:如果allowCoreThreadTimeOutfalse,当线程池的大小达到或超过corePoolSize后,线程池总会留着corePoolSize个线程不会去关闭

    maximumPoolSize:允许存活的最大线程数,超过的线程会被关闭

    keepAliveTime:如果allowCoreThreadTimeOutfalse,总线程数大于核心线程数时,空闲线程允许存活的时间。如果allowCoreThreadTimeOuttrue,不管当前有多少线程,空闲线程允许存活的时间。

  2. 如果workQueue没满,线程池最多只会让corePoolSize个线程同时工作,不会新建线程

  3. 线程运行完一个任务后会通过getTask方法继续获取任务,从而达到线程复用的目的

参考文章

java线程池学习总结

每日一问 | 线程池中的线程是如何复用的?