【詳解】ThreadPoolExecutor原始碼閱讀(二)

貓毛·波拿巴發表於2018-11-01

系列目錄

AQS在Worker中的應用——標識空閒or非空閒工作執行緒

我對這個上鎖一直搞不懂,雖然有註釋說是允許中斷啥的,但是還是一頭霧水,就打算直接看程式碼分析。第一眼看到這個lock的時候,我就嚇到了。

產生了第一個問題:"啥,一上鎖,多個執行緒不是就要同步排隊了嘛? 而且也沒這必要啊! "

看清楚了才知道,鎖來自於方法引數Worker,也就是說,每個執行緒請求的同步鎖都是各自的Worker的鎖,故不存在這些個執行緒競爭一個鎖的情況。

問題又來了我自己的鎖,又沒人跟我搶,犯得著每做一個任務都上鎖嗎?

實際上是有的,只是在這個方法裡,不會發生競爭。

注:此段程式碼同【詳解】ThreadPoolExecutor原始碼閱讀(一) 中的runWorker。

final void runWorker(Worker w) {
    //獲得當前執行這段程式碼的執行緒
    Thread wt = Thread.currentThread();
    //先嚐試從worker取得初始任務
    Runnable task = w.firstTask;
    w.firstTask = null;
    //允許中斷,unlock後state=1,中斷方法獲取到鎖,則判斷為空閒執行緒,可中斷
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        //不斷地取任務執行、 其中getTask提供阻塞。如果getTask返回null則退出迴圈
        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==true
        completedAbruptly = false;
    } finally {
        //工作執行緒退出的處理操作,如獲取當前worker完成的任務量
        //如果異常退出,還需彌補,補充工作執行緒等等
        processWorkerExit(w, completedAbruptly);
    }
}

interruptIdleWorkers 中斷空閒執行緒

註釋不是說了嗎,允許中斷,那肯定跟中斷有關,朝這個方向去找啊。當然,我當時並沒有這樣去找,而是機緣巧合,看到ThreadPoolExecutor其他程式碼的時候突然意識到的。

我先看到了shutdown方法,發現有中斷空閒Worker的方法。但是在此之前,我並不知道執行緒池是如何區別Worker執行緒是空閒還是忙碌的,只知道執行緒池有workers集合用來儲存建立的Worker。

於是,我就順著方法檢視下去。找到關閉空閒Worker方法的實現。

注:shutdown的語義是,關閉執行緒池,停止接收新的任務,繼續執行任務佇列中的任務。中斷多餘的空閒執行緒。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    //獲取執行緒池鎖
    mainLock.lock();
    try {
        //檢查執行執行緒是否有權關閉執行緒池,暫未深入瞭解
        checkShutdownAccess();
        //更改執行緒池執行狀態為SHUTDOWN
        advanceRunState(SHUTDOWN);
        //中斷空閒執行緒
        interruptIdleWorkers();
        //鉤子函式
        onShutdown(); 
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

interruptIdleWorkers方法的註釋:

關閉等待任務的執行緒(也就是沒有被上鎖的執行緒),由此可得Worker有沒有獲得鎖,是區分其是否空閒的標誌。結合原始碼:

private void interruptIdleWorkers(boolean onlyOne) {
    //獲取執行緒池的鎖,保持獨佔訪問
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍歷workers集合中的所有工作執行緒
        for (Worker w : workers) {
            //獲得worker物件中的執行緒引用
            Thread t = w.thread;
            //如果獲得鎖成功,則中斷對應執行緒
            //如果工作執行緒正在執行任務,因為開始執行前,任務會獲取worker的鎖,故其無法被中斷
            //如果工作執行緒正在等待任務,因其沒獲得鎖,則當前執行緒可以獲得其worker的鎖,此工作執行緒被中斷
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            //如果只需要關閉一個工作執行緒,則到此為止
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

 那問題又來了,如何終止已經開始的任務呢?

這裡終止已經開始的任務,就是shutdownNow方法要做的。(shutdownNow就是停止所有任務,已經開始也要停止。其對應的執行緒池狀態是STOP

前面由於無法獲取到Worker的鎖,故無法通過interruptIdleWorkers方法將其中斷。但是ThreadPoolExecutor還提供了interruptWorkers方法,該方法不用獲取鎖,直接呼叫Worker的interruptIfstarted方法中斷執行緒。

 AQS(AbstractQueuedSynchronizer)在Worker中的鎖管理方式

AQS是基於狀態和等待佇列的同步器,這個例項中,Worker繼承於AQS。AQS的acquire依賴於tryAcquire,release依賴於tryAcquire。而這兩個方法它自己都沒有實現,而是由子類提供。(模板方法設計模式的一種體現)。

我們先來看看獲取鎖的操作,以下程式碼來自AQS

public final void acquire(int arg) {
    //注意,java表示式會短路,如果前面的結果使得表示式結果固定,那麼後面的程式碼就不會被執行
    //這裡如果tryAcquire方法返回true, 那麼!tryAcquire就是false,false '與' 任何東西都是false,故後面的表示式不會被執行
    //也就是說如果,一次請求獲取成功,則此方法直接結束,返回。如果請求失敗則加入到等待佇列中,程式碼在這裡停頓
    //如果等待過程被中斷,則中斷當前執行緒
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

AQS沒有提供tryAcquire方法的實現

protected boolean tryAcquire(int arg) {
     throw new UnsupportedOperationException();
}

acquire依賴於tryAcquire方法,如果該方法成功,則acquire方法直接返回,如果失敗,則將當前執行緒加入等待佇列(此操作將park當前執行緒,使其進入waiting狀態)。

我們來看看Worker中是如何實現tryAcquire方法的:

protected boolean tryAcquire(int unused) { //這裡指明瞭引數無用,方法體內都是寫死的
    //利用CAS, 如果當前state值為0,則更改為1
    //如果其他執行緒已獲得鎖,那麼state就是1, 而不是預期的0,則此方法失敗
    if (compareAndSetState(0, 1)) {
        //設定當前獨佔擁有者執行緒
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

我們再來看看release操作,以下程式碼來自AQS:

public final boolean release(int arg) {
    //嘗試釋放,如果釋放成功,則喚醒等待佇列中的第一個執行緒
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

同樣的,AQS沒有實現tryRelease方法

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

Worker中的實現:

protected boolean tryRelease(int unused) {
    //清空當前獨佔擁有者
    setExclusiveOwnerThread(null);
    //設定狀態為0
    setState(0);
    return true;
}

將當前獲取獨佔鎖的執行緒置為null,然後將state為0,這裡與前面tryAcquire一一對應。這個時候其他執行緒就可以獲取鎖了。

 

相關文章