深入JAVA并发编程(九):线程池(二)

ThreadPoolExecutor

在上一篇中我们讲过通过Executors工厂,用户可以创建自己需要的线程池对象,并且了解到newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool等几个线程池都使用了ThreadPoolExecutor实现,只是参数有所不同。

ThreadPoolExecutor,它是JUC在JDK1.5时提供的一种实现了ExecutorService接口的执行器,或者说叫线程池。ThreadPoolExecutor自身并没有直接实现ExecutorService接口,因为它只是其中一种Executor的实现而已,所以Doug Lea把一些通用部分封装成一个抽象父类——AbstractExecutorService,供J.U.C中的其它执行器继承。如果读者需要自己实现一个Executor,也可以继承该抽象类。AbstractExecutorService提供了ExecutorService接口的实现的实现。主要实现了 submit、invokeAny 、invokeAll这三类方法

在这里插入图片描述

ThreadPoolExecutor源码解读

我们先来看下ThreadPoolExecutor的构造函数

	/**
	 * 使用给定的参数创建ThreadPoolExecutor.
	 *
	 * @param corePoolSize    线程池中核心线程最大个数
	 * @param maximumPoolSize 线程池中的最大线程数
	 * @param keepAliveTime   池中线程数>corePoolSize时,多余的空闲线程的存活时间。
	 * @param unit            keepAliveTime的单位
	 * @param workQueue       任务队列, 保存已经提交但尚未被执行的任务
	 * @param threadFactory   线程工厂(用于创建线程)
	 * @param handler         拒绝策略 (当任务太多导致工作队列满时的处理策略)
	 */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

正是通过上述参数的组合变换,使得Executors工厂可以创建不同类型的线程池。

	//例如newFixedThreadPool,就是创建了一个核心线程数和最大线程数都为nThreads的线程池
	//keepAliveTime为0代表只要线程数比核心线程数多并且当前空闲就立即回收
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

线程池状态和线程管理

既然是线程池,那么必然有线程池状态,同时也涉及对其中线程的管理,那么ThreadPoolExecutor是如何做的呢?

ThreadPoolExecutor内部定义了一个AtomicInteger变量——ctl,通过按位划分的方式,在一个变量中记录线程池状态和工作线程数——低29位保存线程数,高3位保存线程池状态。

	//保存线程池状态和工作线程数。
	//低29位:工作线程数。高3位:线程池状态。默认为RUNNING状态,线程个数为0    
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

	//线程个数的位数。
	//并不是所有平台Integer都为32位,准确的说是具体平台下Integer的位数-3的剩余位数表示线程的个数
    private static final int COUNT_BITS = Integer.SIZE - 3;
	
	//线程的最大个数(低29位) 00011111 11111111 11111111 11111111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //线程池状态
    // 11100000 00000000 00000000 00000000
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 00000000 00000000 00000000 00000000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 00100000 00000000 00000000 00000000
    private static final int STOP       =  1 << COUNT_BITS;
    // 01000000 00000000 00000000 00000000
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 01100000 00000000 00000000 00000000
    private static final int TERMINATED =  3 << COUNT_BITS;

	//获取高三位(运行状态)
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //获取低29位(线程个数)
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //计算ctl新值(根据线程状态和线程个数)
    private static int ctlOf(int rs, int wc) { return rs | wc; }

可以看到,ThreadPoolExecutor一共定义了5种线程池状态:

  • RUNNING : 接受新任务, 且处理已经进入阻塞队列的任务
  • SHUTDOWN : 不接受新任务, 但处理已经进入阻塞队列的任务
  • STOP : 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务
  • TIDYING : 所有任务都已终止, 线程池中活动线程数为0, 线程转化为TIDYING状态并准备调用terminated方法
  • TERMINATED : terminated方法已经执行完成

各个状态之间的流转图如下

在这里插入图片描述

另外,我们刚才也提到工作线程(Worker),Worker被定义为ThreadPoolExecutor的内部类,实现了AQS框架,ThreadPoolExecutor通过一个HashSet来保存工作线程:

private final HashSet<Worker> workers = new HashSet<Worker>();
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

通过Worker的定义可以看到,每个Worker对象都有一个Thread线程对象与它相对应,当任务需要执行的时候,实际是调用内部Thread对象的start方法,而Thread对象是在Worker的构造器中通过getThreadFactory().newThread(this)方法创建的,创建的Thread将Worker自身作为任务,所以当调用Thread的start方法时,最终实际是调用了Worker.run()方法,该方法内部委托给runWorker方法执行任务,这个方法我们后面会详细介绍。

线程池的调度流程

ExecutorService的核心方法是submit方法——用于提交一个待执行的任务,我们看ThreadPoolExecutor的源码,会发现它并没有覆写submit方法,而是使用了父类AbstractExecutorServicesubmit的方法,然后自己实现了execute方法:

	//父类AbstractExecutorServicesubmit的方法
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
	//用户提交任务到线程池执行
    public void execute(Runnable command) {
    	//任务为空,抛出异常
        if (command == null)
            throw new NullPointerException();
      
      	//获取ctl值
        int c = ctl.get();
        //如果工作线程数 < corePoolSize,添加新的工作线程并执行
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //执行到此处, 说明工作线程添加失败 或工作线程数≥corePoolSize
        //判断如果线程池处于RUNNING状态,则添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再次检查线程池状态,如果当前线程池不是RUNNING状态则从队列中删除任务
            // 并执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //否则如果当前线程池线程时为0,则添加一个工作线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 插入队列失败, 新增线程,新增失败执行拒绝策略
        else if (!addWorker(command, false))
        	// 执行拒绝策略
            reject(command);
    }

在这里插入图片描述

这里需要特别注意的是 addWorker(null, false) 这个地方,当我们将任务成功添加到队列后,并且二次检查通过后,如果此时的工作线程数为0,就会执行这段代码。为什么呢?

一般来讲每个工作线程(Worker)都有一个Runnable任务和一个对应的执行线程Thread,当我们调用addWorker方法时,如果不传入相应的任务,那么就只是新建了一个没有任务的工作线程(Worker),该Worker就会从工作队列中取任务来执行(因为自己没有绑定任务)。如果传入了任务,新建的工作线程就会执行该任务。所以将任务添加到队列后,需要判断工作线程数是否为0,如果是0那么就必须新建一个空任务的工作线程,将来在某一时刻它会去队列取任务执行,否则没有工作线程的话,该队列中的任务永远不会被执行。

这段代码首先判断如果工作线程数小于核心线程池上限(CorePoolSize),则直接新建一个工作线程并执行任务;
如果工作线程数大于等于CorePoolSize,则尝试将任务加入到队列等待以后执行。这个时候需要判断线程池状态,因为有可能当前线程池已经是非RUNNING状态,非RUNNING状态下是要抛弃新任务的。如果向队列加入任务成功,则还要进行二次校验,因此线程池的状态可能已经改变了,如果此时不是RUNNING状态则移除刚刚添加的任务。如果校验通过,则判断线程池中是否还有线程,如果没有则新增一个线程。如果加入队列失败了(比如队列已满的情况),则在总线程池未满的情况下(CorePoolSize ≤ 工作线程数 < maximumPoolSize)新建一个工作线程立即执行任务,否则执行拒绝策略。

工作线程的创建

了解了ThreadPoolExecutor的整个执行流程,我们来看下它是如何添加工作线程并执行任务的,execute方法内部调用了addWorker方法来添加工作线程并执行任务:

	/**
	 * 添加工作线程并执行任务,如果线程池已停止或符合关闭条件,则此方法返回false
	 * 如果线程工厂在被请求时未能创建线程,它也会返回false。
	 * 如果线程创建失败,或者是由于线程工厂返回空值,或者是由于异常(通常是thread.start()中的OutOfMemoryError),我们将干净地回滚。
	 *
	 * @param firstTask 如果指定了该参数, 表示将立即创建一个新工作线程执行该firstTask任务; 否则复用已有的工作线程,从工作队列中获取任务并执行
	 * @param core      执行任务的工作线程归属于哪个线程池:  true-核心线程池  false-非核心线程池
	 */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取线程池状态
            int rs = runStateOf(c);

         /**
         * 这个if是判断哪些情况下, 线程池不再接受新任务执行, 而是直接返回.
         * 1. 线程池状态为 STOP 或 TIDYING 或 TERMINATED: 线程池状态为上述任一一种时, 都不会再接受任务,所以直接返回
         * 2. 线程池状态为SHUTDOWN 且 firstTask != null: 因为当线程池状态= SHUTDOWN时, 不再接受新任务的提交,所以直接返回
         * 3. 线程池状态为SHUTDOWN 且 队列为空: 队列中已经没有任务了, 所以也就不需要执行任何任务了,可以直接返回
         */
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
			
			//该循环的作用是使用CAS增加线程数
            for (;;) {
            	// 获取工作线程数
                int wc = workerCountOf(c);
             /**
             * 这个if主要是判断工作线程数是否超限, 以下任一情况属于属于超限, 直接返回:
             * 1. 工作线程数超过最大工作线程数(2^29-1)
             * 2. 工作线程数超过核心线程池上限(入参core为true, 表示归属核心线程池)
             * 3. 工作线程数超过总线程池上限(入参core为false, 表示归属非核心线程池)
             */
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // CAS操作使工作线程数加1,成功则直接退出双层循环,向下执行
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  
                // 检测线程池状态是否发生变化,如果是则跳到外层循环重新获取线程池状态重新执行
                // 否则则继续执行内层循环重新CAS
                if (runStateOf(c) != rs)
                    continue retry;
          
            }
        }

		//到这里说明CAS成功增加了线程个数,但是任务还没执行
        boolean workerStarted = false;
        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());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        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) {
                    t.start();
                    workerStarted = true;
                }
            }
        // 创建/启动工作线程失败, 需要执行回滚操作
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

整个addWorker的逻辑并不复杂,分为两部分:
第一部分是一个自旋操作,主要是对线程池的状态进行一些判断,如果状态不适合接受新任务,或者工作线程数超出了限制,则直接返回false。

  • 这里需要注意的就是core参数,为true时表示新建的工作线程在逻辑上归属于核心线程池,所以需要判断条件 工作线程数 < corePoolSize 是否满足;core为false时表示在新增的工作线程逻辑上属于非核心线程池,所以需要判断条件 工作线程数 < maximumPoolSize是否满足。

经过第一部分的过滤,第二部分才真正去创建工作线程并执行任务:
首先将Runnable任务包装成一个Worker对象,然后加入到一个工作线程集合中(名为workers的HashSet),最后调用工作线程中的Thread对象的start方法执行任务,其实最终是委托到Worker的下面方法执行。

工作线程的执行

用户线程提交任务到线程池后,由Worker来执行,我们来看下Worker类。Worker被定义为ThreadPoolExecutor的内部类,实现了AQS框架。ThreadPoolExecutor通过一个HashSet来保存Worker。

	//保存Worker
	private final HashSet<Worker> workers = new HashSet<Worker>();
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;

        Runnable firstTask;

        volatile long completedTasks;


        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

在构造函数中首先将Worker的状态设置位-1,这是为了避免当前Worker在调用runWorker方法前被中断(当其他线程调用了线程池的shutdownNow时,如果Worker状态>=0则会中断该线程。)

通过Worker的定义可以看到,每个Worker对象都有一个Thread线程对象与它相对应,Thread对象是在Worker的构造器中通过getThreadFactory().newThread(this)方法创建的,当开始执行任务时,调用了Worker.run()方法,该方法内部委托给runWorker方法执行任务,我们来看一下该方法。

runWorker方法的整体流程如下:

  • 1.while循环不断地通过getTask()方法从队列中获取任务(如果工作线程自身携带着任务,则执行携带的任务);
  • 2.调用task.run()执行任务
  • 3.处理工作线程的退出工作。
    final void runWorker(Worker w) {
    	// 执行任务的线程
        Thread wt = Thread.currentThread();
        // 获取Worker中的任务, 如果是null则从队列中取任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 将state设置位0,允许执行线程被中断
        w.unlock(); 
        // 表示是否因为中断而导致退出
        boolean completedAbruptly = true;
        try {
        	// 当task==null时会通过getTask从队列取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();

             /**
             * 下面这个if判断的作用如下:
             * 1.保证当线程池状态为STOP/TIDYING/TERMINATED时,当前执行任务的线程wt是中断状态(因为线程池处于上述任一状态时,均不能再执行新任务)
             * 2.保证当线程池状态为RUNNING/SHUTDOWN时,当前执行任务的线程wt不是中断状态
             */
                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;
                    // 完成任务数+1
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	// 处理工作线程的退出工作
            processWorkerExit(w, completedAbruptly);
        }
    }

这里要特别注意第一个if方法,该方法的核心作用,用一句话概括就是:

确保正在停止的线程池(STOP/TIDYING/TERMINATED)不再接受新任务,如果有新任务那么该任务的工作线程一定是中断状态;确保正常状态的线程池(RUNNING/SHUTDOWN),其所执行的任务都是不能被中断的。

另外,getTask方法用于从任务队列中获取一个任务,如果获取不到任务,会跳出while循环,最终会通过processWorkerExit方法清理工作线程。注意这里的completedAbruptly字段,它表示该工作线程是否是因为中断而退出,while循环的退出有以下几种可能:

  • 正常情况下,工作线程会存活着,不断从任务队列获取任务执行,如果获取不到任务了(getTask返回null),会置completedAbruptly 为false,然后执行清理工作——processWorkerExit(worker,false);
  • 异常情况下,工作线程在执行过程中被中断或出现其它异常,会置completedAbruptly 为true,也会执行清理工作——processWorkerExit(worker,true);

工作线程的清理

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
    	// 工作线程因异常情况而退出
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        	// 工作线程数减1(如果工作线程执行时没有出现异常, 在getTask()方法中已经对线程数减1了)
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	// completedTaskCount记录整个线程池完成的总任务数
            completedTaskCount += w.completedTasks;
            // 从工作线程集合中移除(该工作线程会自动被GC回收)
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
		// 根据线程池状态, 判断是否需要终止线程池
        tryTerminate();

		//判断当前线程池线程个数是否小于核心线程数,如果是则新增一个线程
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
        	// 如果线程池状态为RUNNING/SHUTDOWN
        	 // 工作线程为正常退出
            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);
        }
    }

processWorkerExit的作用就是将该退出的工作线程清理掉,然后看下线程池是否需要终止。

processWorkerExit执行完之后,整个工作线程的生命周期也结束了,我们可以通过下图来回顾下它的整个生命周期:

在这里插入图片描述

任务的获取

最后,我们来看下任务的获取,也就是runWorker中使用的getTask方法:

    private Runnable getTask() {
    	// 表示上次从阻塞队列中取任务时是否超时
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            // 获取线程池状态
            int rs = runStateOf(c);
			/**
	         * 以下IF用于判断哪些情况下不允许再从队列获取任务:
	         * 1. 线程池进入停止状态(STOP/TIDYING/TERMINATED), 此时即使队列中还有任务未执行, 也不再执行
	         * 2. 线程池非RUNNING状态, 且队列为空
	         */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();// 工作线程数减1
                return null;
            }

			// 获取工作线程数
            int wc = workerCountOf(c);

	       	/**
	         * timed变量用于判断是否需要进行超时控制:
	         * 对于核心线程池中的工作线程, 除非设置了allowCoreThreadTimeOut==true, 否则不会超时回收;
	         * 对于非核心线程池中的工作线程, 都需要超时控制
	         */
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			// 这里主要是当外部通过setMaximumPoolSize方法重新设置了最大线程数时,需要回收多出的工作线程
            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;
            }
        }
    }

getTask方法的主要作用就是:通过自旋,不断地尝试从阻塞队列中获取一个任务,如果获取失败则返回null。

阻塞队列就是在我们构建ThreadPoolExecutor对象时,在构造器中指定的。由于队列是外部指定的,所以根据阻塞队列的特性不同,getTask方法的执行情况也不同。我们曾经在J.U.C之collections框架系列中全面剖析过J.U.C中的所有阻塞队列:我们可以根据业务需求、任务特点等选择某一种阻塞队列。

任务在阻塞队列中排队一共有三种情况:

(1).直接提交

即直接将任务提交给等待的工作线程,这时可以选择SynchronousQueue。因为SynchronousQueue是没有容量的,而且采用了无锁算法,所以性能较好,但是每个入队操作都要等待一个出队操作,反之亦然。

使用SynchronousQueue时,当核心线程池满了以后,如果不存在空闲的工作线程,则试图把任务加入队列将立即失败(execute方法中使用了队列的offer方法进行入队操作,而SynchronousQueue在调用offer时如果没有另一个线程等待出队操作,则会立即返回false),因此会构造一个新的工作线程(未超出最大线程池容量时)。
由于,核心线程池是很容易满的,所以当使用SynchronousQueue时,一般需要将maximumPoolSizes 设置得比较大,否则入队很容易失败,最终导致执行拒绝策略,这也是为什么Executors工作默认提供的缓存线程池使用SynchronousQueue作为任务队列的原因。

(2).无界任务队列

无界任务队列我们的选择主要有LinkedTransferQueue、LinkedBlockingQueue(近似无界,构造时不指定容量即可),从性能角度来说LinkedTransferQueue采用了无锁算法,高并发环境下性能相对更好,但如果只是做任务队列使用相差并不大。

使用无界队列需要特别注意系统资源的消耗情况,因为当核心线程池满了以后,会首先尝试将任务放入队列,由于是无界队列所以几乎一定会成功,那么系统瓶颈其实就是硬件了。如果任务的创建速度远快于工作线程处理任务的速度,那么最终会导致系统资源耗尽。Executors工厂中创建固定线程池的方法内部就是用了LinkedBlockingQueue。

(3).有界任务队列

有界任务队列,比如ArrayBlockingQueue ,可以防止资源耗尽的情况。当核心线程池满了以后,如果队列也满了,则会创建归属于非核心线程池的工作线程,如果非核心线程池也满了 ,才会执行拒绝策略。

拒绝策略

ThreadPoolExecutor在以下两种情况下会执行拒绝策略:

  • (1).当核心线程池满了以后,如果任务队列也满了,首先判断非核心线程池有没满,没有满就创建一个工作线程(归属非核心线程池), 否则就会执行拒绝策略;
  • (2).提交任务时,ThreadPoolExecutor已经关闭了。

所谓拒绝策略,就是在构造ThreadPoolExecutor时,传入的RejectedExecutionHandler对象:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

ThreadPoolExecutor一共提供了4种拒绝策略:

(1).AbortPolicy(默认)

AbortPolicy策略其实就是抛出一个RejectedExecutionException异常:

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());
    }
}

(2).DiscardPolicy

DiscardPolicy策略其实就是无为而治,什么都不做,等任务自己被回收:

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() {
    }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

(3).DiscardOldestPolicy

DiscardOldestPolicy策略是丢弃任务队列中的最近一个任务,并执行当前任务:

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() {
    }
 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {      // 线程池未关闭(RUNNING)
            e.getQueue().poll();    // 丢弃任务队列中的最近任务
            e.execute(r);           // 执行当前任务
        }
    }
}

(4).CallerRunsPolicy

CallerRunsPolicy策略相当于以自身线程来执行任务,这样可以减缓新任务提交的速度。

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() {
    }
 
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {  // 线程池未关闭(RUNNING)
            r.run();            // 执行当前任务
        }
    }
}

线程池的关闭

ExecutorService接口提供两种方法来关闭线程池,这两种方法的区别主要在于是否会继续处理已经添加到任务队列中的任务。

shutdown方法将线程池切换到SHUTDOWN状态(如果已经停止,则不用切换),并调用interruptIdleWorkers方法中断所有空闲的工作线程,但是阻塞队列里的任务还是会继续执行,该方法会立即返回,不会等待队列任务执行完毕,最后调用tryTerminate尝试结束线程池:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	//权限检查
            checkShutdownAccess();
            //设置线程池状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断所有空闲线程
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

这里要注意,如果执行Runnable任务的线程本身不响应中断,那么也就没有办法终止任务。

shutdownNow方法的主要不同之处就是,它会将线程池的状态至少置为STOP,同时中断所有工作线程(无论该线程是处于中断还是运行中),丢弃队列中的任务,返回值是任务队列中的所有任务。

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

现实情况下,一般会自己通过ThreadPoolExecutor的构造器去构建线程池,而非直接使用Executors工厂创建,因为这样更利于对参数的控制和调优。

阿里开发规范中不允许Executors快速创建线程池,因为可能会造成资源耗尽,例如FixedThreadPool和SingleThreadPool中的阻塞队列最大长度是Integer.MAX_VALUE,可能会造成大量任务累积,造成OOM,还有CachedThreadPool,允许创建的最大线程数是Integer.MAX_VALUE,可能会创建大量线程,同样导致OOM。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值