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

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

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

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

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

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


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