
Flag
ThreadPoolExecutor(線程池),大家使用線程的時候,都用過它對線程進行創(chuàng)建及其調(diào)度管理,想必再熟悉不過。
當我們點開ThreadPoolExecutor源碼,看到這一幕,大多數(shù)人第一感覺會跟我一樣:wtf,這是什么東西。其實仔細看一下,不難理解。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
AtomicInteger這個類是基于CAS,是解決在高并發(fā)情況下原生的int類型自增線程不安全的問題。AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); ctl代表ThreadPoolExecutor中的控制狀態(tài)。默認情況下ctl代表是RUNNING狀態(tài)。
int類型占32位,顯而易見Integer.size等于32。那么COUNT_BITS等于29。再看到(1 << COUNT_BITS) - 1這一行,說明正數(shù)1向左邊移動了29位。想必看到這里,大學學的原碼,補碼,反碼,十進制與二進制,運算符都忘光了吧。我們來回顧一下運算符的知識點。
運算符
-
Java支持的位運算符有7個:-
&:按位與。當兩位同時為1時才返回1。 -
|:按位或。只要有一位為1即可返回1。 -
~:按位非。單目運算符,將操作數(shù)的每個位(包括符號位)全部取反。 -
^:按位異或。當兩位相同時返回0,不同時返回1。 -
<<:左移運算符。 -
>>:右移運算符。 -
>>>:無符號位右移運算符。比如-5>>>2,-5無符號右移動2位后,左邊空出2位后,空出來的2位用0去補充。
-
原碼,反碼,補碼
在計算機中,數(shù)據(jù)都以補碼的形式存在的,數(shù)據(jù)在計算機中都是以二進制存在的。計算機存儲數(shù)據(jù)是以字節(jié)為單位。
無符號數(shù)來說沒有原碼,反碼,補碼之分,因為都相同。正數(shù)的原碼,反碼,補碼都相同。比如+0的原碼是00000000,補碼是00000000,補碼是00000000。(計算機字長為8)
負數(shù)的反碼是對該負數(shù)的原碼(除了符號位)進行按位取反。比如-0的原碼是10000000,反碼是11111111(計算機字長為8)
負數(shù)的補碼 = 負數(shù)的反碼 + 1,比如-0的反碼是11111111,在最后一位進行加1,就變成了00000000。即-0的補碼為00000000
記住計算機規(guī)則都是滿則進一位。
計算機中為什么使用補碼來存儲數(shù)據(jù)
1.看到上面的例子就懂了。+0的原碼和反碼和-0的原碼和反碼都不一樣,而它們的補碼都是一樣的。就是解決了這種不一致的問題。
2.符號位和其他有效值一起進行處理。計算機不適合做減法,用加法代替減法?!綼 - b】補碼= 【a】補碼+ 【-b】補碼,這里值得注意的是,有的計算機中是有乘法器的,有的計算機是用加法代替乘法。
有趣的例子
看到下面java代碼,結(jié)果是多少呢? 答案:-1。首先我們要記住一點就是,-1在計算中補碼表示每位都是1。java中,-1的二進制表示是11111111111111111111111111111111,也就是32個1。
/**
* @author cmazxiaoma
* @version V1.0
* @Description: TODO
* @date 2018/8/16 20:01
*/
public class Test {
public static void main(String[] args) {
System.out.println(~0);
}
}
0的補碼是00000000000000000000000000000000,~0
的意思對0進行按位取反(包括最高位),取反后的結(jié)果應該是11111111111111111111111111111111。我們可以發(fā)現(xiàn)這是一個負數(shù),負數(shù)的反碼 = 補碼 - 1,我們計算出反碼 = 11111111111111111111111111111110。我們對反碼進行按位取反(除了最高位),結(jié)果是10000000000000000000000000000001。最后原碼進行二進制到十進制的轉(zhuǎn)換,結(jié)果是 -1。
想必有了這些基礎,再去分析ThreadPoolExecutor源碼中的成員變量,應該會輕松很多。
回去分析源碼
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
1的補碼是00000000000000000000000000000001,向左移動29個位置,應該變成0010000000000000000000000000000。然后再減去 - 1,通過前面的基礎,我們知道-1的補碼是11111111111111111111111111111111。2個數(shù)值相加的結(jié)果是00011111111111111111111111111111。即capacity的值,我們會發(fā)現(xiàn)高3位都是0,低29位都是1。
通過一系列的運算,我們可以計算出。
RUNNING = 11100000000000000000000000000000
SHUTDOWN = 00000000000000000000000000000000
STOP = 0010000000000000000000000000000
TIDYING = 0100000000000000000000000000000
TERMINATED = 0110000000000000000000000000000
按數(shù)值從小到大排序就是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATE
private static int workerCountOf(int c) { return c & CAPACITY; }
我們計算workerCount的值是通過c & CAPACITY,也就是按位與。明顯是capacity的前3位是000,與c的前3位進行按位與的話,也都000。所以workerCount占據(jù)ctl變量的低29位。
private static int runStateOf(int c) { return c & ~CAPACITY; }
我們計算runState的值是通過先將capacity進行按位非的操作,然后再和c進行按位與的操作~capacity后的值高3位是111,低29位都是0。c的低29位和~capacity的低29位的計算結(jié)果都是0,可以忽略。實際參與計算的是~capacity是高3位(都是1)和c的高3位。所以說runState占據(jù)ctl變量的高3位。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
我們看到ctl初始化方法, 現(xiàn)在很輕松的知道了,runstate為Running狀態(tài),workCount等于0。
進行總結(jié)
workerCount 線程池中當前活動的線程數(shù)量,占據(jù)ctl變量的低29位。
runState 是線程池運行狀態(tài),占據(jù)ctl變量的高3位。有5種狀態(tài):
- RUNNING:接收新的任務,并且隊列中的任務。
- SHUTDOWN:不接收新的任務,但是會處理隊列中的任務和正在運行中的任務。
- STOP:不接收新的任務,也不處理隊列中的任務,中斷正在執(zhí)行中的任務。
- TIDYING:所有任務都已經(jīng)終止,workerCount等于0,將運行terminated()方法
- TERMINATED:terminated()方法執(zhí)行完畢。
我們還要明白線程池運行狀態(tài)的轉(zhuǎn)換
- running 到shutdown的過程:顯式調(diào)用shutdown()方法,或者在線程池中調(diào)用finalize方法。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
/**
* Invokes {@code shutdown} when this executor is no longer
* referenced and it has no threads.
*/
protected void finalize() {
shutdown();
}
- shutdown到stop的過程:調(diào)用了shutdownNow()方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
- shutdown到tidying的過程:線程池和任務隊列都為空。
- stop到tidying的過程:線程池為空。
- terminated的過程: 調(diào)用terminated()方法。
/**
* Method invoked when the Executor has terminated. Default
* implementation does nothing. Note: To properly nest multiple
* overridings, subclasses should generally invoke
* {@code super.terminated} within this method.
*/
protected void terminated() { }
尾言
大家好,我是cmazxiaoma(寓意是沉夢昂志的小馬),希望和你們一起成長進步,感謝各位閱讀本文章。
如果您對這篇文章有什么意見或者錯誤需要改進的地方,歡迎與我討論。
如果您覺得還不錯的話,希望你們可以點個贊。
希望我的文章對你能有所幫助。
有什么意見、見解或疑惑,歡迎留言討論。
最后送上:心之所向,素履以往。生如逆旅,一葦以航。
