`
wx1569618008
  • 浏览: 75239 次
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

java线程池的原理

 
阅读更多

最近对线程池的使用有一些迷惑,比如,线程在使用完,是如何回收的(你不是有回收线程的功能吗???-->后来还是同事替我解惑的,并没有根据源码就直接看出原委来),这篇文章主要讲线程池的使用,然后以简单的使用为入口,进行分析,如果文章中有解释不正确的地方,评论区->请批评我!

目录:

线程池简介

线程池的优点

基本使用

创建线程池

添加任务

关于工作线程

工作线程的任务执行

从队列获取任务

终止线程

关闭线程池

线程池的拒绝策略

蓦然回首

线程池的优点-补充

线程池简介

百度解释:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都是用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后启动。

维基百科对ThreadPool的解释:In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as parallel processors, cores, memory, and network sockets.

我对维基百科的翻译如下:在计算机编程中,线程池是属于计算机程序并发编程中的一种软件设计模式。通常也被称为“replicated workers(重复的工作者)”或者是“worker-crew model”,一个线程池包含多个等待被分配的并发执行的线程。通过持有多个线程的池子模型,避免了线程的频繁创建,以及一些执行短暂任务的线程的频繁销毁,使得性能得到提升。

线程池的优点

  • 因为线程是比较昂贵的资源,避免大量重复创建销毁线程,使用者不用关心创建销毁线程
  • 用户提交的任务能够及时的得到处理,提高响应速度
  • 能够更好的监控和管理线程

基本使用

如何使用Java提供的线程池?以下为基本使用,我们会根据以下代码,进行深入讲解:

ExecutorService executorService = Executors.newFixedThreadPool(5);   
executorService.submit(()-> System.out.println("执行线程"));           
executorService.submit(()-> System.out.println("执行线程2"));          
executorService.shutdown();

以上方式就是使用基本线程池的方式了,我们大致可以理解为首先创建了一个线程池,接着往池子中丢任务,然后关闭池子(先不考虑关闭的时候,任务有没有被执行完),整个过程就是这么简单了。接下来我们开始解析的第一步,如何创建线程池(其实java提供了多种线程池,这里我就先只用这个例子中的线程池进行讲解):

线程池原理

创建线程池

ExecutorService executorService = Executors.newFixedThreadPool(5);   

以下为 Executors.newFixedThreadPool(5); 的内部实现:

/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue.  At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks.  The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());
}

按照寻常人的做法,应该是不会把注释贴上来的,但是,无奈注释给的信息太多了,很多人,包括我自己,以往在读源码的过程中,直接忽略注释,这是一个很不好的习惯,以后我的文章每一篇都会将必要的注释贴上来,这样大家看的时候,也比较方便

以上代码显然是直接创建了一个ThreadPoolExecutor对象(线程池的底层实现),并且带入了默认的0L(我看了下文,这个0是下面的keepAliveTime时间,TimeUnit.MILLISECONDS为时间的单位)我们来看看这几个参数:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

我们来看看核心参数:

  • corePoolSize : 池子(以下内容将会用这个代表线程池)中的线程数,即使是空闲的线程(没干活),也会在池中存在,除非设置allowCoreThreadTimeOut(猜想其是用于标志线程的有效时间,后面我们再看);
  • maximumPoolSize :池子中最多能放多少个线程;
  • keepAliveTime :当线程数大于corePoolSize时,过量的空闲线程将最多等待多长时间,然后被终止;
  • unit :keepAliveTime 时间的单位;
  • workQueue:(比较重要)持有“待执行”的任务队列,该队列只能加入Runnable任务;

主要的参数就解析完了(其实就是注释翻译过来的,很简单)…

该方法中传入的keepAliveTime 为0L,所以,过量的空闲时间将不会有等待时间,而是立马终止;

该构造方法的内部实现:

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);

先不管它调用的是哪儿的方法(明显是另一个构造方法),来看看这里面添加的默认参数:

Executors.defaultThreadFactory();用于生成一个线程工厂(按字面量的意思,实际内部如何工作,等我们看到了使用的地方,再回过头来解析):

public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

defaultHandler 的内容如下,按字面量的意思是拒绝策略(这里我就翻译的比较残忍了,后面会解释):

/**
 * The default rejected execution handler
 */
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

既然参数都已经解释完了,就接着看调用的构造方法:

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这个构造方法就比较核心了,其实也就校验一下参数,使用方法的重载,完成其他参数的设定而已。

这个时候,线程池就创建完毕了(当然里面还有一些细节,后文会详细说明);

添加任务

我们接着看使用线程池的例子:

executorService.submit(() -> System.out.println("执行线程"));
executorService.submit(() -> System.out.println("执行线程2"));

我们调用了executorService的submit方法,但是这个submit却是在AbstractExecutorService类中实现;

以下为submit实现源码:

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

校验参数之后,执行newTaskFor:

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

内部就是是创建FutrueTask (value主要用于任务执行后的结果返回),下面开始execute(ftask)方法解析:

/**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
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(command);
}

根据代码中的注释,其执行步骤分为如下三步:

  • 如果线程数 < corePooiSize, 则尝试创建新的线程来执行当前任务。执行addWorker方法自动检查runState和workerCount变量,如果不符合条件,返回false;
  • 如果该任务成功入队,我们仍然需要双重检测是否应该加入线程,防止当上一次检测完之后,该线程死掉了,或者池子关闭了,所以我们只有再次校验state,有必要的情况下把任务移出队列,或者在当前没有线程的情况下,新建一个线程;
  • 如果任务无法入队,我们尝试创建一个新的线程。如果失败了,那么线程池要么是关闭了,要么是饱和了,那就只好丢弃该任务;
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; }

以上状态值在ThreadPoolExector中定义,我们接着看execute中的执行原理(刚刚也只是翻译了下代码中的注释而已);

int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();
}

如果工作线程数小于corePoolSize,则创建工作线程(这个计算当前线程的值比较绕,orkerCountOf(c) = ((-1 << Integer.SIZE - 3)|0) & ((1 << (Integer.SIZE - 3)) - 1) = 0 ,初始化线程为0),暂不去解析如何创建工作线程(内部实现比较麻烦!)如果创建工作线程成功,则正常返回;

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

校验池子是否是运行中状态,并且尝试向队列尾部中插入该任务成功,再次校验,如果池子已经不再运行,并且从队列中移除任务成功,则直接拒绝任务;如果工作者线程为0,则新建一个空闲的工作线程;

else if (!addWorker(command, false))
        reject(command);

其他情况,则依旧创建工作线程,如果仍然创建失败,则拒绝任务;

接下来看看是如何创建工作线程的:

/**
     * 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
     */
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); 
            //如果工作线程总数已经大于最大容量,或者大于corePoolSize或maximumPoolSize,则直接返回false
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //递增现有工作线程数
            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();
                    //将工作线程加入到池中线程总集合中(Set)
                    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;
}

如果工作线程启动失败,则需要…如下代码:

/**
     * Rolls back the worker thread creation.
     * - removes worker from workers, if present
     * - decrements worker count
     * - rechecks for termination, in case the existence of this
     *   worker was holding up termination
     */
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            //从线程集合中移除该线程
            workers.remove(w);
        //递减工作线程总数
        decrementWorkerCount();
        //尝试置TERMINATED状态
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

到此为止,线程池添加任务完成了,并且任务得以执行,接下来我们就该解决线程是如何复用的问题了,其实我们从一开始介绍的,创建线程池,然后给出任务队列(LinkedBlockingQueue),然后创建任务线程,执行任务,这其中忽略了任务队列以及任务线程本身的讲解,想必大家对任务队列不会感觉特别难理解(先暂时把它当做是一个队列就行),我们接下来开始讲解任务线程本身,也就是工作线程,之前我们讲解的时候只是将工作线程创建完毕加入到线程集合中一笔带阔,现在我们详细了解下这个工作线程,看看它是如何工作的。

回忆一下,前面我们提到创建一个工作线程new Worker,然后执行其封装的Thread的start方法…;

关于工作线程

首先来看看这个Worker的构造(依然是在ThreadPoolExecutor中定义,先不要纠结哪个角色在哪定义,后面我们会详细讲解这整个工作流程中涉及到的角色,以及在哪儿实现的):

    /**
     * Class Worker mainly maintains interrupt control state for
     * threads running tasks, along with other minor bookkeeping.
     * This class opportunistically extends AbstractQueuedSynchronizer
     * to simplify acquiring and releasing a lock surrounding each
     * task execution.  This protects against interrupts that are
     * intended to wake up a worker thread waiting for a task from
     * instead interrupting a task being run.  We implement a simple
     * non-reentrant mutual exclusion lock rather than use
     * ReentrantLock because we do not want worker tasks to be able to
     * reacquire the lock when they invoke pool control methods like
     * setCorePoolSize.  Additionally, to suppress interrupts until
     * the thread actually starts running tasks, we initialize lock
     * state to a negative value, and clear it upon start (in
     * runWorker).
     */
    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(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

首先设置当前线程的状态,标识当前线程的首要任务,这个任务是线程池执行submit的时候传进来的,获取线程工厂,并获取一个新的线程,这里要重点注意getThreadFactory().newThread(this)中的this;

工作线程的任务执行

再来看看其run方法,说起这个run方法,也是之前困扰笔者的地方,因为根据之前的介绍,执行工作线程的代码是:

if (workerAdded) {
    //如果工作线程创建成功,那么就启动线程
    t.start();
    workerStarted = true;
}

这段代码是之前就介绍过的,大家可以看看之前关于如何创建工作线程的介绍,这段代码可以说是把笔者我迷惑的够够的了。直观的看,貌似是直接执行线程的start方法(然后调用传入Worker的Runnable实现的run方法),但是我忽略了Worker的构造方法,没想到Worker中的thread,竟然是将自身作为Runnable实现,构造的线程对象。可以看Worker的继承体系,它实现了Runnable接口,所以自身有run方法,如上代码,执行t.start之后,调用的其实是Worker中的run方法:

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

runWorker的实现并不在Worker类中,而是由ThreadPoolExecutor实现:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //获取worker的工作任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //这里的getTask非常关键,我们暂且任务这是在获取一个任务,因为当前线程首次进入while循环,都是有任务的,所以暂时不会执行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 {
                        //正常执行任务的run方法
                        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 (task != null || (task = getTask()) != null) {
    try {
        beforeExecute(wt, task);
        Throwable thrown = null;
        task.run();
    } finally {
        task = null;
        w.completedTasks++;
        w.unlock();
    }
}

以上代码表明,首次进入时,工作线程应该是携带线程的(当然也有创建不携带任务的工作线程的情况),所以正常来讲,都可以执行run方法,run方法执行完毕之后,worker完成的任务数量递增。接下来重点来了,该线程会继续循环,task ==null的情况下,执行getTask方法,从队列中获取任务,接着执行run方法;

其实runWorker方法上的注释对工作线程执行任务描述的特别清晰,我们看看注释:

   /**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them, while coping with a number of issues:
     *
     * 1. We may start out with an initial task, in which case we
     * don't need to get the first one. Otherwise, as long as pool is
     * running, we get tasks from getTask. If it returns null then the
     * worker exits due to changed pool state or configuration
     * parameters.  Other exits result from exception throws in
     * external code, in which case completedAbruptly holds, which
     * usually leads processWorkerExit to replace this thread.
     *
     * 2. Before running any task, the lock is acquired to prevent
     * other pool interrupts while the task is executing, and then we
     * ensure that unless pool is stopping, this thread does not have
     * its interrupt set.
     *
     * 3. Each task run is preceded by a call to beforeExecute, which
     * might throw an exception, in which case we cause thread to die
     * (breaking loop with completedAbruptly true) without processing
     * the task.
     *
     * 4. Assuming beforeExecute completes normally, we run the task,
     * gathering any of its thrown exceptions to send to afterExecute.
     * We separately handle RuntimeException, Error (both of which the
     * specs guarantee that we trap) and arbitrary Throwables.
     * Because we cannot rethrow Throwables within Runnable.run, we
     * wrap them within Errors on the way out (to the thread's
     * UncaughtExceptionHandler).  Any thrown exception also
     * conservatively causes thread to die.
     *
     * 5. After task.run completes, we call afterExecute, which may
     * also throw an exception, which will also cause thread to
     * die. According to JLS Sec 14.20, this exception is the one that
     * will be in effect even if task.run throws.
     *
     * The net effect of the exception mechanics is that afterExecute
     * and the thread's UncaughtExceptionHandler have as accurate
     * information as we can provide about any problems encountered by
     * user code.
     *
     * @param w the worker
     */

工作线程进入循环,从队列中重复获取任务,并且执行任务:

  1. 当我们首次进入的时候,有一个初始任务,既然如此就不需要从队列中获取了。否则,只要线程池在运行中,就要从池子中获取任务。如果返回null,则工作线程修改池子的状态或配置参数,并退出。
  2. 在执行任务之前,为了在任务执行过程中不被打断,所以需要加锁;如果池子关闭,保证线程被中断,如果没有关闭,确保线程不被中断。
  3. 每个任务执行之前会先调用beforeExecute,当然有可能会抛出异常,如果抛出异常,就会导致线程死掉(跳出循环,并设置completedAbruptly属性值为true),不让它执行任务。
  4. 假设beforeExecute执行顺利,线程开始执行任务,捕获执行过程中的各种异常,然后交给afterExecute处理。我们分开处理Runtime异常以及Error以及其他Throwables。因为我们不能在Runable的run方法中重新抛出异常,所以封装异常..(这段不是特别理解,有懂得人,评论告诉我),任何抛出的异常都会导致线程被杀死。
  5. 在执行完任务的run方法时,调用afterExecute,这个方法同样会抛出异常,同样也会导致线程死掉。

从队列获取任务

我们看下getTask中的实现:

/**
     * Performs blocking or timed wait for a task, depending on
     * current configuration settings, or returns null if this worker
     * must exit because of any of:
     * 1. There are more than maximumPoolSize workers (due to
     *    a call to setMaximumPoolSize).
     * 2. The pool is stopped.
     * 3. The pool is shutdown and the queue is empty.
     * 4. This worker timed out waiting for a task, and timed-out
     *    workers are subject to termination (that is,
     *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
     *    both before and after the timed wait, and if the queue is
     *    non-empty, this worker is not the last thread in the pool.
     *
     * @return task, or null if the worker must exit, in which case
     *         workerCount is decremented
     */
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())) {
            //队列中没有任务了,递减当前工作线程的数量,并且返回null,结束该线程的运行
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        //当核心线程可以在空闲时超时退出,或者当前线程数已经大于核心线程的数量时
        //timed = true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
            //从队列头部获取任务,如果获取不到,则在keepAliveTime时间范围内阻塞
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

以上就是复用线程的原理了,其实困扰笔者的问题也就解决了,其实线程池对线程的回收指的是对池子中的线程进行复用,当线程池中的线程执行完任务之后,它自己自动从任务队列中获取并执行任务,当队列中的任务被执行完之后,线程就直接返回null,那么上面说的while循环就结束了,这个时候工作线程没有执行任务,我们看看程序是如何处理该线程的:

终止线程

    /**
     * Performs cleanup and bookkeeping for a dying worker. Called
     * only from worker threads. Unless completedAbruptly is set,
     * assumes that workerCount has already been adjusted to account
     * for exit.  This method removes thread from worker set, and
     * possibly terminates the pool or replaces the worker if either
     * it exited due to user task exception or if fewer than
     * corePoolSize workers are running or queue is non-empty but
     * there are no workers.
     *
     * @param w the worker
     * @param completedAbruptly if the worker died due to user exception
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // 如果是异常完成的情况
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //将工作线程完成的任务数加入到总数中
            completedTaskCount += w.completedTasks;
            //从Set中移除工作线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        //尝试关闭线程池
        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            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);
        }
    }

关闭线程池

下面看看如何尝试关闭线程池:

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //这几种条件下不能关闭线程池:
        //1. 线程池仍处于运行状态;
        //2. 线程池处于TIDYING 或者 TERMINATE 状态,说明已经在关闭了 不需要重复关闭
        //3. 线程池处于SHUTDOWN 状态并且任务队列不为空,不能终止
        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
    }
}

再看看如何关闭子线程的:

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

线程池如何拒绝任务

在之前分析的任务的执行中,我们有分析过这段代码:

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

这里重点关注下,在reject(command)的时候,程序是如何执行的:

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

handler在ThreadPoolExecutor中的定义如下:

private volatile RejectedExecutionHandler handler;

该类是一个接口类,主要用于描述如何拒绝任务:

public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

我们可以看下这个接口的实现:

  1. 1. AbortPolicy.class 的执行策略是直接抛出异常:

    public static class AbortPolicy implements RejectedExecutionHandler {
       /**
            * Creates an {@code AbortPolicy}.
            */
       public AbortPolicy() { }
    
       /**
            * Always throws RejectedExecutionException.
            *
            * @param r the runnable task requested to be executed
            * @param e the executor attempting to execute this task
            * @throws RejectedExecutionException always
            */
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           throw new RejectedExecutionException("Task " + r.toString() +
                                                " rejected from " +
                                                e.toString());
       }
    }
    
  2. 可以看到,CallerRunsPolicy的执行策略是尝试关闭线程池,如果失败了,则直接执行任务;

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
       /**
            * Creates a {@code CallerRunsPolicy}.
            */
       public CallerRunsPolicy() { }
    
       /**
            * Executes task r in the caller's thread, unless the executor
            * has been shut down, in which case the task is discarded.
            *
            * @param r the runnable task requested to be executed
            * @param e the executor attempting to execute this task
            */
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if (!e.isShutdown()) {
               r.run();
           }
       }
    
  3. DiscardOldestPolicy的执行策略是尝试关闭线程池,如果关闭失败,则把队列中最老的一个任务丢弃,并且执行当前任务;

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
       /**
            * Creates a {@code DiscardOldestPolicy} for the given executor.
            */
       public DiscardOldestPolicy() { }
    
       /**
            * Obtains and ignores the next task that the executor
            * would otherwise execute, if one is immediately available,
            * and then retries execution of task r, unless the executor
            * is shut down, in which case task r is instead discarded.
            *
            * @param r the runnable task requested to be executed
            * @param e the executor attempting to execute this task
            */
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if (!e.isShutdown()) {
               e.getQueue().poll();
               e.execute(r);
           }
       }
    }
    
  4. DiscardPolicy的执行策略是,直接丢弃任务;

    public static class DiscardPolicy implements RejectedExecutionHandler {
       /**
            * Creates a {@code DiscardPolicy}.
            */
       public DiscardPolicy() { }
    
       /**
            * Does nothing, which has the effect of discarding task r.
            *
            * @param r the runnable task requested to be executed
            * @param e the executor attempting to execute this task
            */
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       }
    }
    

    线程池默认的拒绝策略是:

    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
    

蓦然回首

我们再来看一下StackExchange中对线程池解释得评价较高的回答(目前已有84个赞):

A thread pool is a group of pre-instantiated, idle threads which stand ready to be given work. These are preferred over instantiating new threads for each task when there is a large number of short tasks to be done rather than a small number of long ones. This prevents having to incur the overhead of creating a thread a large number of times.

Implementation will vary by environment, but in simplified terms, you need the following:

  • A way to create threads and hold them in an idle state. This can be accomplished by having each thread wait at a barrier until the pool hands it work. (This could be done with mutexes as well.)
  • A container to store the created threads, such as a queue or any other structure that has a way to add a thread to the pool and pull one out.
  • A standard interface or abstract class for the threads to use in doing work. This might be an abstract class called Task with an execute() method that does the work and then returns.

When the thread pool is created, it will either instantiate a certain number of threads to make available or create new ones as needed depending on the needs of the implementation.

When the pool is handed a Task, it takes a thread from the container (or waits for one to become available if the container is empty), hands it a Task, and meets the barrier. This causes the idle thread to resume execution, invoking the execute() method of the Task it was given. Once execution is complete, the thread hands itself back to the pool to be put into the container for re-use and then meets its barrier, putting itself to sleep until the cycle repeats.

https://softwareengineering.stackexchange.com/users/20756/blrfl

这个时候有没有感觉对线程的印象更深了一些?我自己的翻译如下:

线程池的优点-补充

以下内容摘抄维基百科中对ThreadPool进行的介绍:

  • Creating too many threads wastes resources and costs time creating the unused threads.
  • Destroying too many threads requires more time later when creating them again.
  • Creating threads too slowly might result in poor client performance (long wait times).
  • Destroying threads too slowly may starve other processes of resources.

转载于:https://my.oschina.net/u/4026254/blog/2876371

分享到:
评论

相关推荐

    JAVA线程池原理以及几种线程池类型介绍

    ### JAVA线程池原理及几种线程池类型的详细介绍 #### 一、线程池的引入背景及重要性 在现代软件开发中,特别是在基于Java的应用程序设计中,线程池技术已经成为提高系统性能和资源利用率的关键手段之一。线程池...

    JAVA线程池原理以及几种线程池类型介绍.doc

    Java线程池是一种高效管理线程的工具,它允许开发者预先创建一组线程,并复用它们来处理任务,从而降低了创建和销毁线程的开销。线程池的使用尤其适用于处理大量短小任务的场景,例如Web服务器、数据库服务器等。在...

    Java concurrency线程池之线程池原理(一)_动力节点Java学院整理

    Java concurrency线程池之线程池原理(一)_动力节点Java学院整理,动力节点口口相传的Java黄埔军校

    JAVA线程池原理实例详解

    JAVA线程池原理实例详解 JAVA线程池是java中的一种高效的线程管理机制,它可以控制线程的创建、销毁和复用,从而提高系统的性能和可靠性。下面我们将详细介绍JAVA线程池的原理、创建、使用方法及相关注意事项。 一...

    Java 线程池的原理与实现

    理解Java线程池的原理和实现,可以帮助我们更有效地管理并发任务,提升系统性能,同时避免资源浪费和线程安全问题。在实际开发中,合理配置线程池参数,结合业务场景选择合适的线程池类型,是优化系统性能的关键步骤...

    java线程池封装j

    本文将深入探讨Java线程池的原理、封装以及相关知识点。 ### 1. Java线程池概念 Java线程池由`java.util.concurrent`包中的`ExecutorService`接口和其子类实现。其中,最常用的是`ThreadPoolExecutor`类,它提供了...

    JAVA线程池的原理与实现.pdf

    Java线程池是一种高效利用系统资源、管理并发执行任务的机制。...总的来说,理解Java线程池的工作原理和实现对于优化并发应用程序至关重要,它可以帮助我们更好地控制系统的并发度,提高系统的响应速度和资源利用率。

    深入探索:Java线程池的工作原理与实践

    本文将深入探讨Java线程池的工作原理,并通过实际代码示例,展示如何高效地使用线程池。 Java线程池是并发编程中的一个重要工具,它通过减少线程创建和销毁的开销,提高了程序的性能和资源利用率。理解线程池的...

    自定义实现Java线程池

    ### 自定义实现Java线程池 #### 一、概述 在深入探讨自定义Java线程池之前,我们先简要回顾一下线程池的基本概念及其重要性。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动...

    Java常用线程池原理及使用方法解析

    Java线程池是一种高效管理线程的工具,它允许开发者预先创建一组线程,当需要执行新任务时,线程池会从池中选择一个空闲线程来执行任务,而不是每次都创建新的线程。线程池的使用有助于减少线程创建和销毁的开销,...

    Java线程池文档

    Java线程池是一种高效管理线程的机制,它允许开发者预先设定线程的数量,并通过池化的方式重用已创建的线程,以提高系统性能,减少线程的创建和销毁开销。线程池在Java中是通过`java.util.concurrent`包下的`...

    java线程池实例

    Java线程池是一种高效管理线程资源的工具,它通过维护一组可重用的线程来减少创建和销毁线程的开销。在Java中,`java.util.concurrent`包提供了`ExecutorService`接口和它的实现类,如`ThreadPoolExecutor`,来支持...

    深入理解Java线程池(PPT:原理+代码)

    通过剖析Java中线程池的原理,解读Java线程池源码,并给出线程池调用的示例,帮助理解线程池的基本原理。

    Java 线程池原理深入分析

    总结Java线程池是Java多线程编程的重要工具,通过合理的配置和使用,可以有效地管理和调度线程,提高系统性能。理解线程池的工作原理和参数配置,对于编写高效、稳定的多线程程序至关重要。在实际应用中,应根据任务...

    java 线程池实现多并发队列后进先出

    Java线程池是一种高效管理并发任务的机制,它允许开发者预先配置一定数量的线程,以便在处理多个并发任务时能有效地复用这些线程,从而避免了频繁创建和销毁线程带来的开销。在Java中,`java.util.concurrent`包下的...

    JAVA线程池例子

    Java线程池是一种高效管理线程资源的技术,它允许开发者创建一组可重用的工作线程,从而避免频繁地创建和销毁线程带来的性能开销。线程池在Java中主要通过`java.util.concurrent`包中的`ExecutorService`接口及其...

    基于Java线程池技术的数据爬虫设计与实现.pdf

    本文所提及的基于Java线程池技术的数据爬虫设计与实现,不仅涉及到了数据爬虫的原理和架构,还包括了多线程编程的知识点,以及线程池技术在数据爬虫中的具体应用。 首先,数据爬虫的基本原理是模拟用户的点击行为,...

    Java线程池及观察者模式解决多线程意外死亡重启问题

    Java线程池是Java并发编程中的重要组成部分,它允许开发者高效地管理多个并发执行的线程,有效地控制系统的资源消耗,提高系统性能和稳定性。在Java中,`java.util.concurrent`包提供了`ExecutorService`接口及其...

    java多线程详解,线程池原理,8种锁,java内存模型......

    首先希望大家喜欢我制作的文档,如果文档中有什么误解的地方,望告诉一下,5分是也不多,是系统默认的,那么就5分咯,java多线程详解,线程池原理,8种锁,java内存模型......

    Java线程池.pdf

    ### Java线程池详解 #### 引言 在现代计算机科学中,线程作为轻量级的进程,已经成为操作系统和应用程序提高并发性、优化资源...理解和掌握Java线程池的原理和使用方法,对于开发高性能、高可靠的Java应用至关重要。

Global site tag (gtag.js) - Google Analytics