亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Java線程池是如何重復(fù)利用空閑線程的

 更新時(shí)間:2021年06月26日 09:54:54   作者:Java技術(shù)棧  
在Java開(kāi)發(fā)中,經(jīng)常需要?jiǎng)?chuàng)建線程去執(zhí)行一些任務(wù),實(shí)現(xiàn)起來(lái)也非常方便,此時(shí),我們很自然會(huì)想到使用線程池來(lái)解決這個(gè)問(wèn)題,文中給大家提到使用線程池的好處,對(duì)Java線程池空閑線程知識(shí)感興趣的朋友一起看看吧

在Java開(kāi)發(fā)中,經(jīng)常需要?jiǎng)?chuàng)建線程去執(zhí)行一些任務(wù),實(shí)現(xiàn)起來(lái)也非常方便,但如果并發(fā)的線程數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷(xiāo)毀線程需要時(shí)間。此時(shí),我們很自然會(huì)想到使用線程池來(lái)解決這個(gè)問(wèn)題。

使用線程池的好處:

降低資源消耗。java中所有的池化技術(shù)都有一個(gè)好處,就是通過(guò)復(fù)用池中的對(duì)象,降低系統(tǒng)資源消耗。設(shè)想一下如果我們有n多個(gè)子任務(wù)需要執(zhí)行,如果我們?yōu)槊總€(gè)子任務(wù)都創(chuàng)建一個(gè)執(zhí)行線程,而創(chuàng)建線程的過(guò)程是需要一定的系統(tǒng)消耗的,最后肯定會(huì)拖慢整個(gè)系統(tǒng)的處理速度。而通過(guò)線程池我們可以做到復(fù)用線程,任務(wù)有多個(gè),但執(zhí)行任務(wù)的線程可以通過(guò)線程池來(lái)復(fù)用,這樣減少了創(chuàng)建線程的開(kāi)銷(xiāo),系統(tǒng)資源利用率得到了提升。

降低管理線程的難度。多線程環(huán)境下對(duì)線程的管理是最容易出現(xiàn)問(wèn)題的,而線程池通過(guò)框架為我們降低了管理線程的難度。我們不用再去擔(dān)心何時(shí)該銷(xiāo)毀線程,如何最大限度的避免多線程的資源競(jìng)爭(zhēng)。這些事情線程池都幫我們代勞了。

提升任務(wù)處理速度。線程池中長(zhǎng)期駐留了一定數(shù)量的活線程,當(dāng)任務(wù)需要執(zhí)行時(shí),我們不必先去創(chuàng)建線程,線程池會(huì)自己選擇利用現(xiàn)有的活線程來(lái)處理任務(wù)。

很顯然,線程池一個(gè)很顯著的特征就是“長(zhǎng)期駐留了一定數(shù)量的活線程”,避免了頻繁創(chuàng)建線程和銷(xiāo)毀線程的開(kāi)銷(xiāo),那么它是如何做到的呢?我們知道一個(gè)線程只要執(zhí)行完了run()方法內(nèi)的代碼,這個(gè)線程的使命就完成了,等待它的就是銷(xiāo)毀。既然這是個(gè)“活線程”,自然是不能很快就銷(xiāo)毀的。為了搞清楚這個(gè)“活線程”是如何工作的,下面通過(guò)追蹤源碼來(lái)看看能不能解開(kāi)這個(gè)疑問(wèn)。

學(xué)習(xí)過(guò)線程池都知道,可以通過(guò)工廠類(lèi)Executors來(lái)創(chuàng)個(gè)多種類(lèi)型的線程池,部分類(lèi)型如下:

public static ExecutorService newFixedThreadPool(int var0) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
    return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new Executors.DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int var0) {
    return new ScheduledThreadPoolExecutor(var0);
}

無(wú)論哪種類(lèi)型的線程池,最終都是直接或者間接通過(guò)ThreadPoolExecutor這個(gè)類(lèi)來(lái)實(shí)現(xiàn)的。而ThreadPoolExecutor的有多個(gè)構(gòu)造方法,最終都是調(diào)用含有7個(gè)參數(shù)的構(gòu)造函數(shù)。

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

① corePoolSize

顧名思義,其指代核心線程的數(shù)量。當(dāng)提交一個(gè)任務(wù)到線程池時(shí),線程池會(huì)創(chuàng)建一個(gè)核心線程來(lái)執(zhí)行任務(wù),即使其他空閑的核心線程能夠執(zhí)行新任務(wù)也會(huì)創(chuàng)建新的核心線程,而等到需要執(zhí)行的任務(wù)數(shù)大于線程池核心線程的數(shù)量時(shí)就不再創(chuàng)建,這里也可以理解為當(dāng)核心線程的數(shù)量等于線程池允許的核心線程最大數(shù)量的時(shí)候,如果有新任務(wù)來(lái),就不會(huì)創(chuàng)建新的核心線程。

如果你想要提前創(chuàng)建并啟動(dòng)所有的核心線程,可以調(diào)用線程池的prestartAllCoreThreads()方法。

② maximumPoolSize

顧名思義,其指代線程池允許創(chuàng)建的最大線程數(shù)。如果隊(duì)列滿(mǎn)了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會(huì)再創(chuàng)建新的線程執(zhí)行任務(wù)。所以只有隊(duì)列滿(mǎn)了的時(shí)候,這個(gè)參數(shù)才有意義。因此當(dāng)你使用了無(wú)界任務(wù)隊(duì)列的時(shí)候,這個(gè)參數(shù)就沒(méi)有效果了。

③ keepAliveTime

顧名思義,其指代線程活動(dòng)保持時(shí)間,即當(dāng)線程池的工作線程空閑后,保持存活的時(shí)間。所以,如果任務(wù)很多,并且每個(gè)任務(wù)執(zhí)行的時(shí)間比較短,可以調(diào)大時(shí)間,提高線程的利用率,不然線程剛執(zhí)行完一個(gè)任務(wù),還沒(méi)來(lái)得及處理下一個(gè)任務(wù),線程就被終止,而需要線程的時(shí)候又再次創(chuàng)建,剛創(chuàng)建完不久執(zhí)行任務(wù)后,沒(méi)多少時(shí)間又終止,會(huì)導(dǎo)致資源浪費(fèi)。

注意:這里指的是核心線程池以外的線程。還可以設(shè)置allowCoreThreadTimeout = true這樣就會(huì)讓核心線程池中的線程有了存活的時(shí)間。

④ TimeUnit

顧名思義,其指代線程活動(dòng)保持時(shí)間的單位:可選的單位有天(DAYS)、小時(shí)(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)。

⑤ workQueue

顧名思義,其指代任務(wù)隊(duì)列:用來(lái)保存等待執(zhí)行任務(wù)的阻塞隊(duì)列。

⑥ threadFactory

顧名思義,其指代創(chuàng)建線程的工廠:可以通過(guò)線程工廠給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置更加有意義的名字。

⑦ RejectedExecutionHandler

顧名思義,其指代拒絕執(zhí)行程序,可以理解為飽和策略:當(dāng)隊(duì)列和線程池都滿(mǎn)了,說(shuō)明線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務(wù)。這個(gè)策略默認(rèn)情況下是AbortPolicy,表示無(wú)法處理新任務(wù)時(shí)拋出異常。在JDK1.5中Java線程池框架提供了以下4種策略。

AbortPolicy:直接拋出異常RejectedExecutionException。

CallerRunsPolicy:只用調(diào)用者所在線程來(lái)運(yùn)行任務(wù),即由調(diào)用 execute方法的線程執(zhí)行該任務(wù)。

DiscardOldestPolicy:丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。

DiscardPolicy:不處理,丟棄掉,即丟棄且不拋出異常。

這7個(gè)參數(shù)共同決定了線程池執(zhí)行一個(gè)任務(wù)的策略:

當(dāng)一個(gè)任務(wù)被添加進(jìn)線程池時(shí):

  • 線程數(shù)量未達(dá)到 corePoolSize,則新建一個(gè)線程(核心線程)執(zhí)行任務(wù)
  • 線程數(shù)量達(dá)到了 corePools,則將任務(wù)移入隊(duì)列等待
  • 隊(duì)列已滿(mǎn),新建線程(非核心線程)執(zhí)行任務(wù)
  • 隊(duì)列已滿(mǎn),總線程數(shù)又達(dá)到了 maximumPoolSize,就會(huì)由上面那位星期天(RejectedExecutionHandler)拋出異常

說(shuō)白了就是先利用核心線程,核心線程用完,新來(lái)的就加入等待隊(duì)列,一旦隊(duì)列滿(mǎn)了,那么只能開(kāi)始非核心線程來(lái)執(zhí)行了。

上面的策略,會(huì)在閱讀代碼的時(shí)候體現(xiàn)出來(lái),并且在代碼中也能窺探出真正復(fù)用空閑線程的實(shí)現(xiàn)原理。

接下來(lái)我們就從線程池執(zhí)行任務(wù)的入口分析。

一個(gè)線程池可以接受任務(wù)類(lèi)型有Runnable和Callable,分別對(duì)應(yīng)了execute和submit方法。目前我們只分析execute的執(zhí)行過(guò)程。

上源碼:

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) { //第一步:如果線程數(shù)量小于核心線程數(shù)
        if (addWorker(command, true))//則啟動(dòng)一個(gè)核心線程執(zhí)行任務(wù)
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {//第二步:當(dāng)前線程數(shù)量大于等于核心線程數(shù),加入任務(wù)隊(duì)列,成功的話(huà)會(huì)進(jìn)行二次檢查
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);//啟動(dòng)非核心線程執(zhí)行,注意這里任務(wù)是null,其實(shí)里面會(huì)去取任務(wù)隊(duì)列里的任務(wù)執(zhí)行
    }
    else if (!addWorker(command, false))//第三步:加入不了隊(duì)列(即隊(duì)列滿(mǎn)了),嘗試啟動(dòng)非核心線程
        reject(command);//如果啟動(dòng)不了非核心線程執(zhí)行,說(shuō)明到達(dá)了最大線程數(shù)量的限制,會(huì)使用第7個(gè)參數(shù)拋出異常
}

代碼并不多,主要分三個(gè)步驟,其中有兩個(gè)靜態(tài)方法經(jīng)常被用到,主要用來(lái)判斷線程池的狀態(tài)和有效線程數(shù)量:

// 獲取運(yùn)行狀態(tài)
private static int runStateOf(int c)     { return c & ~CAPACITY; }

// 獲取活動(dòng)線程數(shù)
private static int workerCountOf(int c)  { return c & CAPACITY; }

總結(jié)一下,execute的執(zhí)行邏輯就是:

  • 如果 當(dāng)前活動(dòng)線程數(shù) < 指定的核心線程數(shù),則創(chuàng)建并啟動(dòng)一個(gè)線程來(lái)執(zhí)行新提交的任務(wù)(此時(shí)新建的線程相當(dāng)于核心線程);
  • 如果 當(dāng)前活動(dòng)線程數(shù) >= 指定的核心線程數(shù),且緩存隊(duì)列未滿(mǎn),則將任務(wù)添加到緩存隊(duì)列中;
  • 如果 當(dāng)前活動(dòng)線程數(shù) >= 指定的核心線程數(shù),且緩存隊(duì)列已滿(mǎn),則創(chuàng)建并啟動(dòng)一個(gè)線程來(lái)執(zhí)行新提交的任務(wù)(此時(shí)新建的線程相當(dāng)于非核心線程);

從代碼中我們也可以看出,即便當(dāng)前活動(dòng)的線程有空閑的,只要這個(gè)活動(dòng)的線程數(shù)量小于設(shè)定的核心線程數(shù),那么依舊會(huì)啟動(dòng)一個(gè)新線程來(lái)執(zhí)行任務(wù)。也就是說(shuō)不會(huì)去復(fù)用任何線程。在execute方法里面我們沒(méi)有看到線程復(fù)用的影子,那么我們繼續(xù)來(lái)看看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
        }
    }
    //前面都是線程池狀態(tài)的判斷,暫時(shí)不理會(huì),主要看下面兩個(gè)關(guān)鍵的地方
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); // 新建一個(gè)Worker對(duì)象,這個(gè)對(duì)象包含了待執(zhí)行的任務(wù),并且新建一個(gè)線程
        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(); // 啟動(dòng)剛創(chuàng)建的worker對(duì)象里面的thread執(zhí)行
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

方法雖然有點(diǎn)長(zhǎng),但是我們只考慮兩個(gè)關(guān)鍵的地方,先是創(chuàng)建一個(gè)worker對(duì)象,創(chuàng)建成功后,對(duì)線程池狀態(tài)判斷成功后,就去執(zhí)行該worker對(duì)象的thread的啟動(dòng)。也就是說(shuō)在這個(gè)方法里面啟動(dòng)了一個(gè)關(guān)聯(lián)到worker的線程,但是這個(gè)線程是如何執(zhí)行我們傳進(jìn)來(lái)的runnable任務(wù)的呢?接下來(lái)看看這個(gè)Worker對(duì)象到底做了什么。

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

最重要的構(gòu)造方法:

Worker(Runnable firstTask) { // worker本身實(shí)現(xiàn)了Runnable接口
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask; // 持有外部傳進(jìn)來(lái)的runnable任務(wù)
        //創(chuàng)建了一個(gè)thread對(duì)象,并把自身這個(gè)runnable對(duì)象給了thread,一旦該thread執(zhí)行start方法,就會(huì)執(zhí)行worker的run方法
        this.thread = getThreadFactory().newThread(this); 
    }
在addWorker方法中執(zhí)行的t.start會(huì)去執(zhí)行worker的run方法:

public void run() {
        runWorker(this);
    }
run方法又執(zhí)行了ThreadPoolExecutor的runWorker方法,把當(dāng)前worker對(duì)象傳入。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 取出worker的runnable任務(wù)
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 循環(huán)不斷的判斷任務(wù)是否為空,當(dāng)?shù)谝粋€(gè)判斷為false的時(shí)候,即task為null,這個(gè)task啥時(shí)候?yàn)閚ull呢?
        // 要么w.firstTask為null,還記得我們?cè)趀xecute方法第二步的時(shí)候,執(zhí)行addWorker的時(shí)候傳進(jìn)來(lái)的runnable是null嗎?
        // 要么是執(zhí)行了一遍while循環(huán),在下面的finally中執(zhí)行了task=null;
        // 或者執(zhí)行第二個(gè)判斷,一旦不為空就會(huì)繼續(xù)執(zhí)行循環(huán)里的代碼。
        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(); // 任務(wù)不為空,就會(huì)執(zhí)行任務(wù)的run方法,也就是runnable的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; // 執(zhí)行完成置null,繼續(xù)下一個(gè)循環(huán)
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

方法比較長(zhǎng),歸納起來(lái)就三步:

1,從worker中取出runnable(這個(gè)對(duì)象有可能是null,見(jiàn)注釋中的解釋?zhuān)?/p>

2,進(jìn)入while循環(huán)判斷,判斷當(dāng)前worker中的runnable,或者通過(guò)getTask得到的runnable是否為空,不為空的情況下,就執(zhí)行run;

3,執(zhí)行完成把runnable任務(wù)置為null。

假如我們不考慮此方法里面的while循環(huán)的第二個(gè)判斷,在我們的線程開(kāi)啟的時(shí)候,順序執(zhí)行了runWorker方法后,當(dāng)前worker的run就執(zhí)行完成了。

既然執(zhí)行完了那么這個(gè)線程也就沒(méi)用了,只有等待虛擬機(jī)銷(xiāo)毀了。那么回顧一下我們的目標(biāo):Java線程池中的線程是如何被重復(fù)利用的?好像并沒(méi)有重復(fù)利用啊,新建一個(gè)線程,執(zhí)行一個(gè)任務(wù),然后就結(jié)束了,銷(xiāo)毀了。沒(méi)什么特別的啊,難道有什么地方漏掉了,被忽略了?

仔細(xì)回顧下該方法中的while循環(huán)的第二個(gè)判斷(task = getTask)!=null

玄機(jī)就在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);

        // timed變量用于判斷是否需要進(jìn)行超時(shí)控制。
        // allowCoreThreadTimeOut默認(rèn)是false,也就是核心線程不允許進(jìn)行超時(shí);
        // wc > corePoolSize,表示當(dāng)前線程池中的線程數(shù)量大于核心線程數(shù)量;
        // 對(duì)于超過(guò)核心線程數(shù)量的這些線程或者允許核心線程進(jìn)行超時(shí)控制的時(shí)候,需要進(jìn)行超時(shí)控制
        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果需要進(jìn)行超時(shí)控制,且上次從緩存隊(duì)列中獲取任務(wù)時(shí)發(fā)生了超時(shí)(timedOut開(kāi)始為false,后面的循環(huán)末尾超時(shí)時(shí)會(huì)置為true)
        // 或者當(dāng)前線程數(shù)量已經(jīng)超過(guò)了最大線程數(shù)量,那么嘗試將workerCount減1,即當(dāng)前活動(dòng)線程數(shù)減1,
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // 如果減1成功,則返回null,這就意味著runWorker()方法中的while循環(huán)會(huì)被退出,其對(duì)應(yīng)的線程就要銷(xiāo)毀了,也就是線程池中少了一個(gè)線程了
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 注意workQueue中的poll()方法與take()方法的區(qū)別
            //poll方式取任務(wù)的特點(diǎn)是從緩存隊(duì)列中取任務(wù),最長(zhǎng)等待keepAliveTime的時(shí)長(zhǎng),取不到返回null
            //take方式取任務(wù)的特點(diǎn)是從緩存隊(duì)列中取任務(wù),若隊(duì)列為空,則進(jìn)入阻塞狀態(tài),直到能取出對(duì)象為止
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true; // 能走到這里說(shuō)明已經(jīng)超時(shí)了
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

注釋已經(jīng)很清楚了,getTask的作用就是,在當(dāng)前線程中:

1,如果當(dāng)前線程池線程數(shù)量大于核心線程數(shù)量或者設(shè)置了對(duì)核心線程進(jìn)行超時(shí)控制的話(huà)(此時(shí)相當(dāng)于對(duì)所有線程進(jìn)行超時(shí)控制),就會(huì)去任務(wù)隊(duì)列獲取超時(shí)時(shí)間內(nèi)的任務(wù)(隊(duì)列的poll方法),獲取到的話(huà)就會(huì)繼續(xù)執(zhí)行任務(wù),也就是執(zhí)行runWorker方法中的while循環(huán)里的任務(wù)的run方法,執(zhí)行完成后,又繼續(xù)進(jìn)入getTask從任務(wù)隊(duì)列中獲取下一個(gè)任務(wù)。如果在超時(shí)時(shí)間內(nèi)沒(méi)有獲取到任務(wù),就會(huì)走到getTask的倒數(shù)第三行,設(shè)置timeOut標(biāo)記為true,此時(shí)繼續(xù)進(jìn)入getTask的for循環(huán)中,由于超時(shí)了,那么就會(huì)進(jìn)入嘗試去去對(duì)線程數(shù)量-1操作,-1成功了,就直接返回一個(gè)null的任務(wù),這樣就回到了當(dāng)前線程執(zhí)行的runWorker方法中,該方法的while循環(huán)判斷getTask為空,直接退出循環(huán),這樣當(dāng)前線程就執(zhí)行完成了,意味著要被銷(xiāo)毀了,這樣自然就會(huì)被回收器擇時(shí)回收了。也就是線程池中少了一個(gè)線程了。因此只要線程池中的線程數(shù)大于核心線程數(shù)(或者核心線程也允許超時(shí))就會(huì)這樣一個(gè)一個(gè)地銷(xiāo)毀這些多余的線程。

2,如果當(dāng)前活動(dòng)線程數(shù)小于等于核心線程數(shù)(或者不允許核心線程超時(shí)),同樣也是去緩存隊(duì)列中取任務(wù),但當(dāng)緩存隊(duì)列中沒(méi)任務(wù)了,就會(huì)進(jìn)入阻塞狀態(tài)(隊(duì)列的take方法),直到能取出任務(wù)為止(也就是隊(duì)列中被新添加了任務(wù)時(shí)),因此這個(gè)線程是處于阻塞狀態(tài)的,并不會(huì)因?yàn)榫彺骊?duì)列中沒(méi)有任務(wù)了而被銷(xiāo)毀。這樣就保證了線程池有N個(gè)線程是活的,可以隨時(shí)處理任務(wù),從而達(dá)到重復(fù)利用的目的。

綜上所述,線程之所以能達(dá)到復(fù)用,就是在當(dāng)前線程執(zhí)行的runWorker方法中有個(gè)while循環(huán),while循環(huán)的第一個(gè)判斷條件是執(zhí)行當(dāng)前線程關(guān)聯(lián)的Worker對(duì)象中的任務(wù),執(zhí)行一輪后進(jìn)入while循環(huán)的第二個(gè)判斷條件getTask(),從任務(wù)隊(duì)列中取任務(wù),取這個(gè)任務(wù)的過(guò)程要么是一直阻塞的,要么是阻塞一定時(shí)間直到超時(shí)才結(jié)束的,超時(shí)到了的時(shí)候這個(gè)線程也就走到了生命的盡頭。

然而在我們開(kāi)始分析execute的時(shí)候,這個(gè)方法中的三個(gè)部分都會(huì)調(diào)用addWorker去執(zhí)行任務(wù),在addWorker方法中都會(huì)去新建一個(gè)線程來(lái)執(zhí)行任務(wù),這樣的話(huà)是不是每次execute都是去創(chuàng)建線程了?事實(shí)上,復(fù)用機(jī)制跟線程池的阻塞隊(duì)列有很大關(guān)系,我們可以看到,在execute在核心線程滿(mǎn)了,但是隊(duì)列不滿(mǎn)的時(shí)候會(huì)把任務(wù)加入到隊(duì)列中,一旦加入成功,之前被阻塞的線程就會(huì)被喚醒去執(zhí)行新的任務(wù),這樣就不會(huì)重新創(chuàng)建線程了。

我們用個(gè)例子來(lái)看下:

假設(shè)我們有這么一個(gè)ThreadPoolExecutor,核心線程數(shù)設(shè)置為5(不允許核心線程超時(shí)),最大線程數(shù)設(shè)置為10,超時(shí)時(shí)間為20s,線程隊(duì)列是LinkedBlockingDeque(相當(dāng)于是個(gè)無(wú)界隊(duì)列)。

當(dāng)我們給這個(gè)線程池陸續(xù)添加任務(wù),前5個(gè)任務(wù)執(zhí)行的時(shí)候,會(huì)執(zhí)行到我們之前分析的execute方法的第一步部分,會(huì)陸續(xù)創(chuàng)建5個(gè)線程做為核心線程執(zhí)行任務(wù),當(dāng)前線程里面的5個(gè)關(guān)聯(lián)的任務(wù)執(zhí)行完成后,會(huì)進(jìn)入各自的while循環(huán)的第二個(gè)判斷getTask中去取隊(duì)列中的任務(wù),假設(shè)當(dāng)前沒(méi)有新的任務(wù)過(guò)來(lái)也就是沒(méi)有執(zhí)行execute方法,那么這5個(gè)線程就會(huì)在workQueue.take()處一直阻塞的。這個(gè)時(shí)候,我們執(zhí)行execute加入一個(gè)任務(wù),即第6個(gè)任務(wù),這個(gè)時(shí)候會(huì)進(jìn)入execute的第二部分,將任務(wù)加入到隊(duì)列中,一旦加入隊(duì)列,之前阻塞的5個(gè)線程其中一個(gè)就會(huì)被喚醒取出新加入的任務(wù)執(zhí)行了。(這里有個(gè)execute的第二部分的后半段執(zhí)行重復(fù)校驗(yàn)的代碼即addWorker(傳入null任務(wù)),目前還沒(méi)搞明白是怎么回事)。

在我們這個(gè)例子中,由于隊(duì)列是無(wú)界的,所以始終不會(huì)執(zhí)行到execute的第三部分即啟動(dòng)非核心線程,假如我們?cè)O(shè)置隊(duì)列為有界的,那么必然就會(huì)執(zhí)行到這里了。

小結(jié)

通過(guò)以上的分析,應(yīng)該算是比較清楚地解答了“線程池中的核心線程是如何被重復(fù)利用的”這個(gè)問(wèn)題,同時(shí)也對(duì)線程池的實(shí)現(xiàn)機(jī)制有了更進(jìn)一步的理解:

當(dāng)有新任務(wù)來(lái)的時(shí)候,先看看當(dāng)前的線程數(shù)有沒(méi)有超過(guò)核心線程數(shù),如果沒(méi)超過(guò)就直接新建一個(gè)線程來(lái)執(zhí)行新的任務(wù),如果超過(guò)了就看看緩存隊(duì)列有沒(méi)有滿(mǎn),沒(méi)滿(mǎn)就將新任務(wù)放進(jìn)緩存隊(duì)列中,滿(mǎn)了就新建一個(gè)線程來(lái)執(zhí)行新的任務(wù),如果線程池中的線程數(shù)已經(jīng)達(dá)到了指定的最大線程數(shù)了,那就根據(jù)相應(yīng)的策略拒絕任務(wù)。

當(dāng)緩存隊(duì)列中的任務(wù)都執(zhí)行完了的時(shí)候,線程池中的線程數(shù)如果大于核心線程數(shù),就銷(xiāo)毀多出來(lái)的線程,直到線程池中的線程數(shù)等于核心線程數(shù)。此時(shí)這些線程就不會(huì)被銷(xiāo)毀了,它們一直處于阻塞狀態(tài),等待新的任務(wù)到來(lái)。

注意: 本文所說(shuō)的“核心線程”、“非核心線程”是一個(gè)虛擬的概念,是為了方便描述而虛擬出來(lái)的概念,在代碼中并沒(méi)有哪個(gè)線程被標(biāo)記為“核心線程”或“非核心線程”,所有線程都是一樣的,只是當(dāng)線程池中的線程多于指定的核心線程數(shù)量時(shí),會(huì)將多出來(lái)的線程銷(xiāo)毀掉,池中只保留指定個(gè)數(shù)的線程。那些被銷(xiāo)毀的線程是隨機(jī)的,可能是第一個(gè)創(chuàng)建的線程,也可能是最后一個(gè)創(chuàng)建的線程,或其它時(shí)候創(chuàng)建的線程。一開(kāi)始我以為會(huì)有一些線程被標(biāo)記為“核心線程”,而其它的則是“非核心線程”,在銷(xiāo)毀多余線程的時(shí)候只銷(xiāo)毀那些“非核心線程”,而“核心線程”不被銷(xiāo)毀。這種理解是錯(cuò)誤的。

原文鏈接:https://blog.csdn.net/anhenzhufeng/article/details/88870374

到此這篇關(guān)于詳解Java線程池是如何重復(fù)利用空閑線程的的文章就介紹到這了,更多相關(guān)Java線程池空閑線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)文件分片上傳接口的示例代碼

    Java實(shí)現(xiàn)文件分片上傳接口的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)文件分片上傳的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2022-07-07
  • 如何利用java實(shí)現(xiàn)生成PDF文件

    如何利用java實(shí)現(xiàn)生成PDF文件

    前段時(shí)間因?yàn)橄嚓P(guān)業(yè)務(wù)需求需要后臺(tái)生成pdf文件,下面這篇文章主要給大家介紹了關(guān)于如何利用java實(shí)現(xiàn)生成PDF文件的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • JDBC連接MySQL數(shù)據(jù)庫(kù)批量插入數(shù)據(jù)過(guò)程詳解

    JDBC連接MySQL數(shù)據(jù)庫(kù)批量插入數(shù)據(jù)過(guò)程詳解

    這篇文章主要介紹了JDBC連接MySQL數(shù)據(jù)庫(kù)批量插入數(shù)據(jù)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Java下載安裝和環(huán)境變量配置圖文教程

    Java下載安裝和環(huán)境變量配置圖文教程

    這篇文章主要為大家詳細(xì)介紹了Java下載安裝和環(huán)境變量配置圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • OpenTelemetry初識(shí)及調(diào)用鏈Trace詳解

    OpenTelemetry初識(shí)及調(diào)用鏈Trace詳解

    這篇文章主要為為大家介紹了OpenTelemetry初識(shí)及調(diào)用鏈Trace詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • java批量導(dǎo)入導(dǎo)出文件的實(shí)例分享(兼容xls,xlsx)

    java批量導(dǎo)入導(dǎo)出文件的實(shí)例分享(兼容xls,xlsx)

    這篇文章主要給大家介紹了利用java批量導(dǎo)入導(dǎo)出文件的相關(guān)資料,文中給出了詳細(xì)的實(shí)例代碼,并且兼容xls,xlsx,對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,下面跟著小編一起來(lái)看看詳細(xì)的介紹吧。
    2017-06-06
  • druid ParserException類(lèi)錯(cuò)誤問(wèn)題及解決

    druid ParserException類(lèi)錯(cuò)誤問(wèn)題及解決

    這篇文章主要介紹了druid ParserException類(lèi)錯(cuò)誤問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Retrofit+Rxjava實(shí)現(xiàn)文件上傳和下載功能

    Retrofit+Rxjava實(shí)現(xiàn)文件上傳和下載功能

    這篇文章主要介紹了Retrofit+Rxjava實(shí)現(xiàn)文件上傳和下載功能,文中提到了單文件上傳和多文件上傳及相關(guān)參數(shù)的請(qǐng)求,需要的朋友參考下吧
    2017-11-11
  • Java 繼承與多態(tài)的深入理解

    Java 繼承與多態(tài)的深入理解

    這篇文章主要介紹了Java 繼承與多態(tài)的深入理解的相關(guān)資料,子類(lèi)繼承父類(lèi)的特征和行為,使得子類(lèi)具有父類(lèi)的各種屬性和方法?;蜃宇?lèi)從父類(lèi)繼承方法,使得子類(lèi)具有父類(lèi)相同的行為,需要的朋友可以參考下
    2017-08-08
  • 如何在Java中創(chuàng)建線程通信的四種方式你知道嗎

    如何在Java中創(chuàng)建線程通信的四種方式你知道嗎

    開(kāi)發(fā)中不免會(huì)遇到需要所有子線程執(zhí)行完畢通知主線程處理某些邏輯的場(chǎng)景?;蛘呤蔷€程 A 在執(zhí)行到某個(gè)條件通知線程 B 執(zhí)行某個(gè)操作。下面我們來(lái)一起學(xué)習(xí)如何解決吧
    2021-09-09

最新評(píng)論