ThreadPoolExecutor複用流程的一點理解

ThreadPoolExecutor複用流程的一點理解

一、背景

​ 最近在學併發編程的線程池,看了很多文章和視頻,我認爲都沒有講到線程池的核心。大家都說線程池能減少創建線程的消耗和銷燬時間,但是卻不解釋是怎麼做到的,而僅僅只是不斷重複講如果大於核心線程數會怎麼樣,大於最大線程數會這麼樣。。感覺本末倒置了。我覺得得研究一下,不然線程池在我眼裏就是個多餘的東西。

二、前提知識

​ 這個可能只是我自己一直以來的誤解,我一直以爲線程池裏面可以放不同類型的線程類,所以我一很疑惑,就算線程能複用,但是不同線程的類的run方法不一樣啊?怎麼複用?當然我的想法是錯的。線程池中的線程類就是一個,比如ExecutorService pool = Executors.newFixedThreadPool(4); pool.execute(xxx);這個xxx只能是一類,一個相同的類。

三、關鍵代碼

​ 首先我們理清execute這個方法背後的執行順序,我只一一按順序列出,知道關鍵的地方纔會加以特別說明:

  • execute()

  • addworker(w)

  • Work a = new Worker(w)

    work重寫了run方法,也就是runwork()

  • a.start()

    以上就是一個線程添加到線程池的邏輯順序,這個並不能體現複用。我們接着看,到了a.start()這個一步,線程就會去執行run(),work重寫了run,也就跑到了runwork()裏面:這個是關鍵。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 第一次進來的時候task不爲空
            // 第二進進來的時候因爲final執行的原因,task爲null,就會執行gettask了
            while (task != null || (task = getTask()) != null) {
                w.lock();
                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);
        }
    }

本質是什麼呢:可以看見這個線程的run方法是個while循環,其中getTask會被阻塞住,這個我們後面講 ,因此其實就是該線程不會結束。

下面就要看看那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 {
                // 前面的不用管,看着在這行判斷。
                // timed 如果當前線程數 大於核心線程數,那麼就爲true
                // 當前線程數大於核心線程數,那麼就把當前線程放到隊列中
                // 當前線程數小於核心線程數,就去隊列中拿去
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

可以用文字來描述,一個線程execute的過程(假設該線程池核心數量2個),比方說線程A,第一次進來,會添加到work數組中,然後執行run,然後到gettask發現隊列是空的,他的queue.take()因此一直阻塞着(因此線程不會結束),再來個線程B,一樣執行完然後在queue.tak()阻塞,再來一個線程C,因爲現在大於核心線程數,他會經過一個隊列的offer方法(見下面的代碼),把該線程放到隊列中,因此前面兩個線程的take就可以拿到線程了,於是複用了!通過debug就發現這個c線程沒有經過start()了,於是減少了線程創建的時間!妙!

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	// 但第三個線程進入的時候,就大於核心線程數目了,因此會執行offer!
    	// 明顯的但offer執行失敗了,也就是隊列滿了,就會到下一個else if
        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);
        }
    	// 只要不超過max線程,那麼,就還會繼續創建線程,是個全新的線程,直接運行start
        else if (!addWorker(command, false))
            reject(command);
    }

四、小結

回過頭來思考:複用的本質就是,while循環讓run方法不停止,然後配合隊列進行阻塞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章