前言
各行各業(yè)都有基本功,例如醫(yī)生,需要知道人體各個器官、各個系統(tǒng)的作用,知道細胞的作用、細菌和真菌的區(qū)別、病毒是怎么形成的,還得知道各種藥的作用,如何對癥下藥等。
在程序員世界里,也有著體現(xiàn)程序員基本功的東西,例如數(shù)據(jù)結(jié)構(gòu)、算法、操作系統(tǒng)、網(wǎng)絡等,特別是當今互聯(lián)網(wǎng),飛速發(fā)展下,迅速搞產(chǎn)品快速上線的時代已經(jīng)不是主流,而且當下行業(yè)不景氣;大廠們招聘標準上對基本功也是越來越看重,基本功的修煉已經(jīng)刻不容緩。
所以,我想通過一種訪談的形式來慢慢揭開程序員各種基本功的細節(jié),分享給一起同行的你們!!
嘉賓介紹
今天我們很榮幸的邀請到了一位JAVA世界的老前輩,ThreadPoolExecutor,Java線程團隊Leader,被Doug Lea創(chuàng)造,2004年9月30日18:00PM,隨著J2SE1.5的發(fā)布后,被大眾熟知;在Java世界的15年里,他兢兢業(yè)業(yè)堅守著自己的崗位,在提高系統(tǒng)多核資源利用方面有著豐富的經(jīng)驗。
采訪
作為線程團隊的Leader,您是否了解您的團隊成員?(ps:什么是線程?)

其實我的團隊成員都不是我招收進來的,他們都是由我的大BOSS(早期程序員開荒者)招進來;說到了解的話,這就得從我的大BOOS招來的操作系統(tǒng)老大說起;
操作系統(tǒng)指揮我們的程序干活主要靠以下幾種算法:
- 先來先服務和短作業(yè)(進程)優(yōu)先調(diào)度算法
- 高優(yōu)先權(quán)優(yōu)先調(diào)度算法
- 優(yōu)先權(quán)調(diào)度算法:非搶占式優(yōu)先權(quán)算法和搶占式優(yōu)先權(quán)算法
- 高響應比優(yōu)先調(diào)度算法
- 基于時間片的輪轉(zhuǎn)調(diào)度算法:時間片輪轉(zhuǎn)法和多級反饋隊列調(diào)度算法
大部分操作系統(tǒng)(如windows、linux)的任務調(diào)度是采用時間片輪轉(zhuǎn)的搶占式調(diào)度方式,系統(tǒng)把所有就緒進程按先入先出的原則排成一個隊列。新來的進程加到就緒隊列末尾。每當執(zhí)行進程調(diào)度時,進程調(diào)度程序總是選出就緒隊列的隊首進程,讓它在CPU上運行一個時間片的時間。在一個時間片結(jié)束時,發(fā)生時鐘中斷,調(diào)度程序據(jù)此暫停當前進程的執(zhí)行,將其送到就緒隊列的末尾,并通過上下文切換執(zhí)行當前的隊首進程,進程可以未使用完一個時間片,就讓出CPU

進程是什么?
進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進程既是基本的分配單元,也是基本的執(zhí)行單元。進程由內(nèi)存空間(代碼、數(shù)據(jù)、進程空間、打開的文件)和一個或多個線程組成。
介紹完上述的操作系統(tǒng)任務調(diào)度方式和進程的概念,我們再來聊聊我的團隊成員到底是什么樣的存在?
其實有進程就能滿足一個程序的正常執(zhí)行了,那么為什么還需要我和我的團隊呢?其實是因為隨著計算機的快速發(fā)展,對CPU的要求越來越高,進程之間切換的開銷較大,已經(jīng)無法滿足越來越復雜的程序的要求了。我的大BOSS為了能支撐公司的發(fā)展,就叫操作系統(tǒng)BOSS去解決這個問題,操作系統(tǒng)BOSS就不得不尋找一種新人才,于是線程就誕生了。線程是程序執(zhí)行中一個單一的順序控制流程,是程序執(zhí)行流的最小單元,是處理器調(diào)度和分派的基本單位。 有了線程也不代表代替了進程的地位,操作系統(tǒng)BOSS也比較聰明,讓進程來指揮線程,一個進程可以有一個或多個線程,各個線程之間共享程序的內(nèi)存空間(也就是所在進程的內(nèi)存空間)。
一個標準的線程由線程ID、當前指令指針(PC)、寄存器和堆棧組成
您日常的工作有哪些呢?(ps:線程池到底做了什么?)
簡單的說,我的工作就是來管理線程的,為啥需要管理呢?一般來說,我的團隊成員也就那么10幾個(ps:CPU密集型一般是N+1,I/O密集型一般是2N+1,其中N是CPU總核數(shù)),公司招一個人的成本也是很高的(ps:線程創(chuàng)建時間),而線程們也不會自己干活,他們沒事的時候就休息著,等到有任務過來時,我就找正在休息的人來干活。
- 線程的創(chuàng)建、銷毀是非常耗時的,可能比實際執(zhí)行任務的運行時間還要長。
- 線程池就是對一定數(shù)量的線程保持其存活狀態(tài),不讓其銷毀,有新的任務來了就直接調(diào)用空閑線程來執(zhí)行,這樣就可以提升線程的運行效率。
能談談您具體是怎么管理您的團隊成員么?(如何管理調(diào)度線程執(zhí)行)
我的管理方式主要是依靠以下幾個寶物:
- 狀態(tài)控制器ctl:用于管理線程池狀態(tài)和工作者線程個數(shù)
- 線程池大小配置corePoolSize和maximumPoolSize:決定了最大能運行多少線程
- 阻塞隊列workQueue:當無空閑線程時,暫存在阻塞隊列,等待poll出執(zhí)行
狀態(tài)控制器ctl
怎么去判斷大家什么時候是休息狀態(tài),什么時候是工作狀態(tài)呢;這就靠我的狀態(tài)控制器ctl,它包含兩部分:
- 線程池狀態(tài)runState:通過這個狀態(tài)我就能知道什么時候能分配任務,什么時候該下班休息
- 工作者線程個數(shù):workCount
這里先介紹兩個常量和獲取這兩部分信息的方式:
// 將32位int分割為3和29,前三位用于存儲線程池狀態(tài),后面的位數(shù)表示工作者線程個數(shù) int COUNT_BITS = Integer.SIZE - 3; // 工作者線程個數(shù),最大為2^29-1 int CAPACITY = (1 << COUNT_BITS) - 1; /** 1. runState:高三位來代表線程池狀態(tài),runState 2. workCount:表示工作者個數(shù) */ int runStateOf(int c) { return c & ~CAPACITY; } int workerCountOf(int c) { return c & CAPACITY; } int ctlOf(int rs, int wc) { return rs | wc; }
線程池狀態(tài)主要有以下幾種:
- RUNNING = -1 << COUNT_BITS:允許接入新的task或處理workQueue中的task
- SHUTDOWN = 0 << COUNT_BITS:不接受新的task,但是能處理workQueue中的task
- STOP = 1 << COUNT_BITS:不接受新task,也不處理workQueue中的task,同時還會interrupt當前運行的task
- TIDYING = 2 << COUNT_BITS:當狀態(tài)變?yōu)門IDYING將會調(diào)起terminated()方法:所有task都被中斷,workerCount=0;
- TERMINATED = 3 << COUNT_BITS:terminated()方法執(zhí)行完成
其中TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
狀態(tài)轉(zhuǎn)換過程為:
- RUNNING -> SHUTDOWN:shutdown()方法被調(diào)用時
- (RUNNING or SHUTDOWN) -> STOP:shutdownNow()方法被調(diào)用時
- SHUTDOWN -> TIDYING:當workQueue為空且線程池的運行線程為0時
- STOP -> TIDYING:當線程池的運行線程為0時
- TIDYING -> TERMINATED:當terminated()方法執(zhí)行完成
如下圖:

線程調(diào)度
整個線程調(diào)度以來上述幾個法寶:線程池大小、阻塞隊列和狀態(tài)控制器,具體思路如下:
- 如果正在運行的線程數(shù)量小于 corePoolSize,那么馬上創(chuàng)建線程運行這個任務;
- 如果正在運行的線程數(shù)量大于或等于 corePoolSize,那么將這個任務放入隊列;
- 如果這時候隊列滿了,而且正在運行的線程數(shù)量小于 maximumPoolSize,那么還是要創(chuàng)建非核心線程立刻運行這個任務;
- 如果隊列滿了,而且正在運行的線程數(shù)量大于或等于 maximumPoolSize,那么線程池會交由RejectedExecutionHandler來處理
如下圖:

下面讓我們看看具體實現(xiàn):

addWorker:創(chuàng)建一個新的Worker用于執(zhí)行Runnable


Worker的run方法實際調(diào)用的是ThreadPoolExecutor#runWorker方法:如果工作者本身帶有task則執(zhí)行,否則會從阻塞隊列workQueue中poll中一個task進行執(zhí)行
