parallelstream启动的线程数_线程池源码解析

对于一个程序员来讲,线程池肯定都不陌生,但是对与线程池的原理相信有很多小伙伴就不太了解了,不知道你是否想过一个问题当我们new一个线程实例并调用该线程的start方法启动线程,任务执行结束那么线程就只有等待死亡了,那么线程池又是如何创建线程并且重复利用线程的呢?小编也曾迷惑过,下面我们就打开源码一起看看线程池到底是怎么去管理线程的;

  • 一、线程创建

1、首先我们先创建一个线程池并提交一个任务看下任务执行的效果;

f3a4683f61868c2201c4982e15efca5b.png

代码很简单,问题来了线程池到底是在哪一步创建的线程呢?我们先看下实例化线程池这段代码:

//创建一个线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(36,36,60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(60));

进入ThreadPoolExecutor 构造函数看下里面是怎么写的:

9b6a52756aa3aaf167fb2f634be3d40e.png

一步一步点进去最终会执行到下面这个新构造方法:

4e395dcb7ba46a76ba79f9d52ae67c61.png

在这个构造方法中:

1、首先对corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲时存活的最大时间)、workQueue(线程队列)、threadFactory(线程工厂)、handler(拒绝策略)参数做了合法性校验;

2、给线程池的属性赋值;

看到这里我们可以总结为线程池被创建时除了给属性赋值别的啥都没干,那继续往下看提交任务这个方法

00f5343eddc5aa7ac3559c07f32ab579.png

线程池对象调用了父类(AbstractExecutorService)中的submit方法;

e178575b02b407a243170a978597ae8e.png
RunnableFuture是Runnable的一个子类

然后紧接着调用线程池(ThreadPoolExecutor)的execute方法

853b4c906afd3a384267b2817acc6a8c.png

看到这里代码是不是很熟悉,这块代码可以分为三块来看

第一块:意思是当一个任务被提交时首先会去检查线程池中的工作线程是否小于核心线程,如果是则调用addWorker方法创建新的线程;

第二块:当前线程池中核心线程已满,但是任务队列未满,则添加到队列中;

第三块:当前线程池中核心线程已满,任务队列也满了,就尝试调用addWorker创建新的线程(非核心),如果创建线程失败则执行拒绝策略;

不难看出上边代码的核心就在于addWorker()方法,好奇addWorker方法到底干了啥呢?addWorker点进去继续看

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

看上去代码比较长,for循环鄙人认为是对线程池运行状态的检查和CAS算法进行线程数+1,这里可以选择暂时忽略,有兴趣的可以自己研究下;

继续往下看:

 w = new Worker(firstTask);
 final Thread t = w.thread;

这两行代码是重点,运行到这里我们发现线程池才为我们创建了线程;

  • 二、线程的启动

线程已经创建完成了,那线程又是怎么启动的呢? 顺着上面的代码继续往下看,你会看到一行很熟悉的代码

t.start();

执行到这儿线程池中的线程就被启动了;虽然我们已经了解了线程池中线程是怎么被创建和启动的,但是我们一开始文章中的疑惑并没有得到答案,那就是线程池是怎么样重复利用线程的;

想要了解这个问题恐怕我们要详细的了解下这个Worker类了

  • 三、线程池是怎么循环利用线程提高效率的
 private final class Worker extends AbstractQueuedSynchronizer implements Runnable

从源码上看Worker这个类实现了Runnable接口,当new 一个Worker实例对象时;

5b64611145055431b3d7e4b4e95579b9.png

我们会看到线程工厂创建线程会把这个当前类对象,也就是Worker实例当作一个任务透传给线程,addWorder这个方法调用线程start时会启动Worker中的run()方法

4b583ef6567984cf91638c2f4579ae0f.png
 /**
     * 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
     */
    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 = 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);
        }
    }

想搞明白我们的疑惑,理解runWorker方法是关键,在这块代码里我们会发现里面有个循环,循环的条件是task != null || (task = getTask()) != null,假如task不为空,我们进入循环会发现这么一行代码:执行任务;

task.run();

继续再往下看你会发现有个finally代码块:

finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }

代码的中task被重新赋值为空;然后执行下一循环;不知道你是否疑惑刚才循环条件中方法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?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

这个方法中重点看下

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

你会发现这个方法实际上是在从队列中获取任务;OK ,到这里是不是很清楚了,循环从队列中获取任务并赋值给task,然后调用task的run方法,以此循环。。。。这样对于处理队列里的任务就不需要重新new 一个线程来处理了,因此实现了线程的重复利用;

总结下,从我对代码的了解,当线程池提交一个任务时,只要线程池中的线程数没有达到核心线程数量,不管是否存在空闲的线程,线程池都会重新创建一个新的线程去处理任务,只有当任务队列里任务数不为空时才会重复利用线程池中空闲的线程;

以上仅代表个人对源码的理解,过程如有分析不对的地方,望知友不吝赐教,共同进步,非常感谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值