线程池原理

207 阅读11分钟

关于多线程

  1. 为什么要使用多线程? 提高效率
  2. 项目中哪些地方使用多线程? 高并发项目中使用多线程
  3. 多线程处理会有哪些问题? 多线程同时共享一个全局变量,可能会被其他线程干扰,从而产生线程安全问题,无法获取正确的结果,还有可能发生死锁
  4. 怎么解决线程的安全问题? 使用lock,Synchronized,CAS(乐观)等
  5. 死锁的问题怎么解决? 可以采用诊断工具检测。

为什么使用线程池

如果我们频繁的创建活销毁线程,对CPU的消耗是非常大的,需要考虑多线程的复用机制。其思路是创建线程,执行了run方法的逻辑后,不会立刻停止线程,而是继续复用执行下一个任务,可以使用一个线程执行多个不同的任务,从而实现复用机制。

  1. 怎么保证线程不停止呢 写一个死循环
  2. 线程池的优点? · 复用线程,减少频繁创建 · 提高程序的效率,减少CPU的调度

线程池的原则

  1. 如果当前线程数少于核心线程数,那么新的请求你总是会新创建新的线程执行
  2. 如果当前线程数大于核心线程数,那么新提交的请求会放入阻塞队列
  3. 如果请求无法被加入到队列中,如果超过了最大线程数,则执行饱和策略,否则新建非核心线程执行请求

任务排队

  1. 直接交接任务。 有一个不错的默认选择是队列SynchronousQueue,该队列用于叫任务转交给线程,而不是持有该任务。在这里,如果没有立即可用的线程,尝试将任务排队来运行它将失败,因此将构建一个新线程。 任务直接交接通常需要无大小限制的maximumPoolSize,这样能避免新任务被拒绝执行。
  2. 无边界队列。 无边界队列不需要指定容量,例如LinkedBlockingQueue,当所有的核心线程都在使用时,新的任务都会在队列中等待。所以小于核心线程数的线程才被创建,所以maximumPoolSize这个值没有什么意义。
  3. 有界队列 有界队列,例如ArrayBlockingQueue,有助于防止资源耗尽,它与有限的maximumPoolSizes一起使用,但可能更难调整和控制。 队列大小和最大池大小可以交替使用:使用大队列和小池可最大程度地减少 CPU使用率,操作系统资源和上下文切换开销,但会导致人为地降低吞吐量。如果任务经常阻塞例如,如果它们受I / O约束),系统可能可以安排时间超出了您的允许范围。使用小队列通常需要更大的池大小,这会使CPU繁忙,但可能会遇到无法接受的调度开销,这也降低吞吐量。

任务拒绝

Executor被关闭后,或者线程池达到最大线程数并且饱和了,通过execute(Runnable)提交的任务会被拒绝,无论哪种case,execute会调用 RejectedExecutionHandler#rejectedExecution(Runnable, ThreadPoolExecutor)}。提供四种预定义的拒绝策略

  1. ThreadPoolExecutor.AbortPolicy 默认的拒绝策略,程序会抛一个运行时异常,RejectedExecutionException
  2. ThreadPoolExecutor.CallerRunsPolicy 执行execute的线程会运行这个策略,这是个简单的反馈机制,以此来减慢任务提交的频率
  3. ThreadPoolExecutor.DiscardPolicy 不能被执行的任务会被丢弃。
  4. ThreadPoolExecutor.DiscardOldestPolicy 如果线程池没有关闭,那么工作队列中的头部任务将被丢弃,然后重试执行。

线程池的五种状态

生命周期转换如下

execute过程

基本的过程如下图所示

www.processon.com/view/link/5…

关键变量 线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。在实现中,线程池将运行状态(runState)、线程数量(workerCount)两个关键参数的维护放在了一起,而在实现中,用一个AtomicInteger类型来标识两个值,低29位保存保存workerCount,高3位保存runState,而且在获取是通过位运算的标识方式,这种做法不仅节省了锁资源,而且相对于基本运算,速度也提升很多。代码如下所示:

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; }
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 should not, 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();
        1.如果 workerCount < corePoolSize,那么创建并启动一个线程来执行新提交的任
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        2.如果workerCount >= corePoolSize,且阻塞队列未满,则将任务添加到阻塞队列中
        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);
        }
        3.如果workerCount >= corePoolSize且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务
        else if (!addWorker(command, false))
        4.如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
            reject(command);
    }
  1. 如果当前运行线程数少于核心线程数,那么启动新线程执行任务
  2. 如果一个任务成功入队了,仍然需要双重检查一下
  3. 如果无法插入队列,则尝试新建一个线程执行,如果失败,则是线程池关闭了或者是处于饱和状态

addWorker方法

/**
     * Checks if a new worker can be added with respect to current
     * pool state and the given bound (either core or maximum). If so,
     * the worker count is adjusted accordingly, and, if possible, a
     * new worker is created and started, running firstTask as its
     * first task. This method returns false if the pool is stopped or
     * eligible to shut down. It also returns false if the thread
     * factory fails to create a thread when asked.  If the thread
     * creation fails, either due to the thread factory returning
     * null, or due to an exception (typically OutOfMemoryError in
     * Thread.start()), we roll back cleanly.
     *
     * @param firstTask the task the new thread should run first (or
     * null if none). Workers are created with an initial first task
     * (in method execute()) to bypass queuing when there are fewer
     * than corePoolSize threads (in which case we always start one),
     * or when the queue is full (in which case we must bypass queue).
     * Initially idle threads are usually created via
     * prestartCoreThread or to replace other dying workers.
     *
     * @param core if true use corePoolSize as bound, else
     * maximumPoolSize. (A boolean indicator is used here rather than a
     * value to ensure reads of fresh values after checking other pool
     * state).
     * @return true if successful
     */
    **core 是否是核心线程**
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            队列为空是必要条件,证明所有的任务都执行完毕
            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;
                通过CAS workerCouont自加1
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                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 {
            创建线程
            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中,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;
    }

以上就是增加任务的操作,那么我们现在看下线程池是怎么把保证核心线程不死的,以及非核心线程的超时设置是怎么用的。带着这些问题,我们来继续看下代码

核心线程复用和非核心线程超时设置原理

这个主要看下Worker这个类就好了,在上述代码中,w = new Worker(firstTask);中创建了Worker实例,来看下这个类

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{
        .....
 Worker(Runnable firstTask) {
            构造方法中初始化了两个变量,firstTask 和 由线程工厂创建的线程
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

下面来关注一下关键的run方法,

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

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        很明显,如果这个while一直执行的话,那么这个线程就不会被销毁,也就能达到复用的目的
        如果当前的task不为null,那么直接当前的task,如果为null,则通过getTask()去阻塞队列里获取任务
            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();
                }
            }
            completedAbruptly = false;
        } finally {
            回收线程操作
            processWorkerExit(w, completedAbruptly);
        }
    }

根据以上注释,我们可以看到,线程之所以能够维持复用的原因就在于while这个循环,任务执行结束后,在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?
            allowCoreThreadTimeOut是核心线程超时设置,默认核心线程没有超时限制
            如果工作线程数大于核心线程数则为true,否则为false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

            try {
            根据上述注释,如果timed为false,则为核心线程,执行workQueue的take操作,这是一个阻塞操作,所以核心线程不死的原因就在这里。
            如果timer为true,则为非阻塞线程,那么会等待keepAliveTime的时间,如果超过keepAliveTime这个时间仍然未获得任务,则该线程执行完毕,销毁。否则执行队列中的任务。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

总结

  1. 线程池为了掌握线程的状态并维护线程的生命周期,设计了线程池内的工作线程Worker
  2. Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask。thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。
  3. 。Worker被创建出来后,就会不断地进行轮询(通过getTask()),然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。