1

线程创建

要了解关闭,首先要了解创建。

ThreadPoolExecutor有几个关键的参数:

  • corePoolSize:线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程,否则刚启动时,线程池的线程数为0。
  • workQueue:用来保存等待执行的任务的阻塞队列。当提交任务时,如果线程数已经达到corePoolSize,任务会被放入阻塞队列里等待执行。
  • maximumPoolSize:线程池中允许的最大线程数。线程池的阻塞队列满了之后,还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果workQueue使用的是无界队列,该参数也就没有什么效果了。
  • keepAliveTime:线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务且没有新任务可执行时,并不会立即销毁,而是继续存活一段时间。默认情况下,核心线程即使空闲也不会被回收,有参数可以设置让核心线程也参与回收。
  • RejectedExecutionHandler:拒绝策略。当前阻塞队列已满,且线程数已达到最大线程数时,根据配置的策略决定如何处理任务。



ThreadPoolExecutor提交新任务时,分以下几种情况:

  1. 当前已创建的线程数小于核心线程数,创建新线程执行任务。
  2. 当前已创建的线程数大于等于核心线程数:

    1. 阻塞队列未满,添加任务到阻塞队列中,等待执行。
    2. 阻塞队列已满:

      1. 当前已创建的线程数小于最大线程数,创建新线程执行任务。
      2. 当前已创建的线程数等于最大线程数,执行拒绝策略。


线程复用

Thread跟Runnable是一一对应的,Thread.start实际上是调Runnable.run,当run方法执行完,Thread也就结束要回收了。那么ThreadPoolExecutor是如何做到线程复用:让一个Thread能执行多个任务(Runnable)的方法?

下面贴出提交任务时的几段代码:

public class ThreadPoolExecutor extends AbstractExecutorService {
    public void execute(Runnable command) {
        ......
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        ......
}

通过提交任务入口,看到添加任务是在addWorker方法内。

public class ThreadPoolExecutor extends AbstractExecutorService {
    private boolean addWorker(Runnable firstTask, boolean core) {
        ......
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            .....
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
}

这里可以看到,线程池是在提交的Runnable外面套一层Worker

public class ThreadPoolExecutor extends AbstractExecutorService {

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

        public void run() {
            runWorker(this);
        }     
    } 
    
    final void runWorker(Worker w) {
        Runnable task = w.firstTask;
        w.firstTask = null;
        ...
        try {
            while (task != null || (task = getTask()) != null) {
                ...
                task.run();
                ...
            }
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
}           

Worker也实现了Runnable方法,在启动(run)时,会先把创建时传过来的Runnable对象执行,执行完之后再循环去任务列表里获取其他未执行的任务(getTask)。所以线程池里Thread是跟Worker一一对应,Worker里再循环执行多个任务。


线程停止

ThreadPoolExecutor怎么停止当前正在执行中的线程?

回到本文的主题,从ThreadPoolExecutor的两个停止方法入手:

shutdown

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();  // ㈠
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);  // ㈡
        interruptIdleWorkers();  // ㈢
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();  // ㈣
    }
    tryTerminate();  // ㈤
}

其中的两步通过锁保证线程安全。
步将线程状态改为SHUTDOWN。线程池共用以下五种状态:

  1. RUNNING:处于RUNNING状态的线程池能够接受新任务,以及对新添加的任务进行处理。
  2. SHUTDOWN:处于SHUTDOWN状态的线程池不可以接受新任务,但是已添加的任务(阻塞队列里的任务)仍然会被处理。
  3. STOP:处于STOP状态的线程池不接收新任务,不处理已添加的任务。
  4. TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。
  5. TERMINATED:线程池彻底终止的状态。

步调用interruptIdleWorkers方法中断空闲中的Worker

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

这里可以看到,方法遍历所有的Worker,获取锁之后,最终中断是通过Thread.interrupt来实现的。


那这里是怎么判断线程是否空闲?


我们回到Worker这个类。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    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);
    }

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

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

}

final void runWorker(Worker w) {
    ...
    w.unlock(); // allow interrupts
    ...
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            ...
            try {
                ...
                task.run();
                ...
            } finally {
                ...
                w.unlock();
            }
        }
        ...
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

Worker继承了AbstractQueuedSynchronizerAbstractQueuedSynchronizer JUC下很多类的父类,有个state字段。
Worker加锁-释放锁的操作如下:

  1. tryLock -> tryAcquire(1) -> compareAndSetState(0, 1)state值从0变成1
  2. lock -> acquire(1) -> tryAcquire(1) -> compareAndSetState(0, 1)state值从0变成1
  3. unlock -> release -> tryRelease -> setState(0)state值设为0

Worker启动后锁状态变化如下:

  1. Worker创建的时候,将state初始化为-1
  2. 启动时:runWorker -> w.unlockstate值设为0,。
  3. 执行任务时:getTask() -> w.lock -> task.run() -> w.unlock(),在获取到任务之后将state值设为1,执行完任务之后将state值设为0

通过以上可以得出state值对应的线程状态:

  • -1:初始状态,线程还没启动。
  • 0:线程已执行完任务,还没执行新任务。(空闲,已释放锁)
  • 1:线程正在执行任务。(繁忙,拥有锁)

再回到interruptIdleWorkers方法, 可以看到在t.interrupt()之前调用了w.tryLock(),如果这里成功获取到锁,就表明当前线程是空闲的,但如果线程正在执行,w.tryLock()申请不到锁,也就执行不到t.interrupt()

shutdownNow

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

private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

步将线程状态改为STOP
步调用interruptWorkers方法中断所有Worker(不仅仅是空闲的Worker)。
drainQueue获取并清理所有等待执行(还未被线程执行)的任务。

再进入interruptWorkers

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

通过state > 0确认当前线程已经启动(前面有提到,state初始状态是-1,线程启动之后在01之间来回切换),之后也是通过Thread.interrupt来中断线程。



到这里,可以知道shutdownshutdownNow的差别在于:

  • shutdown:不再接收新任务,“已提交但未执行的任务”会继续被执行,中断的是空闲中的线程。
  • shutdownNow:不再接收新任务,“已提交但未执行的任务”也不会再执行,中断所有线程。

shutdownshutdownNow方法的最后一步都是tryTerminate,将状态最终变成TERMINATED

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}


线程中断

interrupt肯定会让线程结束吗?

回到shutdown方法,runWorker时,线程循环:“取任务 -> 加锁 -> 执行任务 -> 释放锁 -> 取任务 -> ...”,在释放锁加锁中间有间隙,如果这时候shutdown刚好获取到锁,执行了interrupt,那线程是不是就结束了,如果该线程刚好是线程池最后一个线程,那是不是task里还未执行的任务就永远不会被执行了?

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

回答这个问题前,我们再看下以下代码会打印几遍“begin”和“end”。

Thread thread = new Thread(() -> {
    int count = 2;
    for(int i = 0; i < count; i++) {
        System.out.println("begin");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception ignore) {
        }
        System.out.println("end");
    }
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
thread.join();

答案是两遍。说明线程被interrupt后并不一定就会结束。
进入Threadinterrupt方法源码,方法上注解说明:

  • sleepwaitjoin等方法,会抛出InterruptedException(注意这里并没有设置中断状态)。
  • InterruptibleChannel的I/O方法将会抛出ClosedByInterruptException,并设置中断状态。
  • Selector阻塞被中断,会设置中断状态,并回到selection operation
  • 如果前面的条件都不成立,那么这个线程的中断将设置状态。

可以看到,除了特定的方法之外,interrupt仅仅是将该线程的中断状态设置为true,并不会影响任务的执行。
比如以下代码,thread.join()会被一直阻塞到数据库sleep60秒执行完。

Thread thread = new Thread(() -> {
    // 数据库执行'select sleep(60)'
    db.sleep60();
});
thread.start();
TimeUnit.SECONDS.sleep(5);
thread.interrupt();
thread.join();

interrupt并不是粗暴的停掉线程,而是友好地告诉线程:“在方便且愿意的时候停止当前正在做的事”。
这时候我们回到前面的问题,即使shutdown刚好在unlock-lock的间隙获取到了锁并执行了interrupt,也不影响线程继续执行(getTask方法里自己内部有捕获了InterruptedException)。


最后我们来看下下面这段代码有什么问题?

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            try {
                // 业务代码
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
        }
    }
}

这段代码的问题是:这个线程永远不会被停掉,可能会影响到项目/服务的正常停止。
可以改成以下方式:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 业务代码
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}


以上源码基于JDK1.8。

noname
317 声望51 粉丝

一只菜狗