1. 程式人生 > 實用技巧 >程式設計之美!從執行緒池狀態管理來看二進位制操作之美

程式設計之美!從執行緒池狀態管理來看二進位制操作之美

二進位制操作在框架設計中被頻繁使用,使用二進位制在不同場景有提升計算速度較少記憶體佔用等多種優點;

下面,我們依據執行緒池的狀態管理,來看下怎麼通過操作二進位制對狀態進行管理,過程中會發現程式設計之美~

執行緒池狀態

首先,為了文章的完整性,我們還是先了解一下執行緒池的狀態,總結如下如:

執行緒池狀態分為5種RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED

狀態代表的含義

  • RUNNING:(執行)接收新task,並且處理正在排隊的task,不中斷正在執行的任務
  • SHUTDOWN:(關閉)不接受新的task,只處理正在排隊的task,不中斷正在執行的任務
  • STOP:(停止)不接受新的task,也不處理正在排隊的task,並且中斷正在執行的任務
  • TIDYING:(整理)所有的task都已經終止,上述提到的workCount當前活躍執行緒數為0,被中斷的任務和正在排隊的任務執行當前任務的terminated()鉤子方法
  • TERMINATED:(已終止)標識上述的TIDYING的過程結束,標識當前執行緒池成功完全停止的狀態

狀態轉換

大致的流程就是:

RUNNING --> SHUTDOWN --> STOP --> TIDYING --> TERMINATED

上述流程是一個單方向的順序,也就是說不會出現類似於STOP --> SHUTDOWN 這種情況;

另外,並不是每一個狀態多必須經過的;

什麼時候進行執行緒池的狀態轉換呢?

  • RUNNING -> SHUTDOWN:呼叫終止執行緒的方法shutdown()
  • RUNNING or SHUTDOWN -> STOP:呼叫shutdownNow()方法後,不管當前在RUNNING狀態還是SHUTDOWN狀態,都是直接轉為STOP狀態
  • SHUTDOWN -> TIDYING:SHUTDOWN狀態下當等待佇列 和 正在執行的任務 都為空時,狀態轉為TIDYING
  • STOP -> TIDYING:STOP狀態下當正在執行的任務全部中斷完畢後,狀態轉為TIDYING
  • TIDYING -> TERMINATED:TIDYING狀態下當所有的terminated()鉤子方法全部執行完畢後,狀態轉為TERMINATED,執行緒池關閉完畢!

管理執行緒池狀態

執行緒池中管理執行緒池狀態 和 執行緒池當前活躍執行緒數,是通過一個AtomicInteger變數來管理這兩個狀態的

什麼? 一個變數管理兩個這麼不相干的狀態? 對的;

CTL變數何許人也

讓我們來看一下執行緒池針對這部分的實現:

    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; }
    private static boolean isRunning(int c) {  return c < SHUTDOWN;}

下面,我們來剖析一下上述的實現:
執行緒池包含5種狀態如下:具體執行緒的狀態代表的含義和狀態的轉換,下面會有講解:

    private static final int COUNT_BITS = Integer.SIZE - 3;

    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;

我們知道在java中 int 型別佔用4個位元組32位儲存, 上述的幾種狀態:
底層儲存二進位制為:

1111 1111 1111 1111 1111 1111 1111 1111(-1)
0000 0000 0000 0000 0000 0000 0000 0000(0)
0000 0000 0000 0000 0000 0000 0000 0001(1)
0000 0000 0000 0000 0000 0000 0000 0010(2)
0000 0000 0000 0000 0000 0000 0000 0011(3)

左移<<COUNT_BITS位 COUNT_BITS = Integer.SIZE - 3 也就是 COUNT_BITS = 29,改句子說明用32位的前3位儲存執行緒池的狀態
後29位儲存執行緒池中當前執行緒的個數, << COUNT_BITS後,變為下面的二進位制:

1110 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0010 0000 0000 0000 0000 0000 0000 0000
0100 0000 0000 0000 0000 0000 0000 0000
0110 0000 0000 0000 0000 0000 0000 0000

我們可以看到,前三位儲存的是 標識執行緒狀態的二進位制

對於初始化儲存這些狀態的變數 AtomicInteger ctl

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0))

初始化AtomicInteger變數ctl,其中ctlOf(RUNNING, 0) 程式碼為:

private static int ctlOf(int rs, int wc) { return rs | wc; }

其中rs標識執行緒池當前狀態,wc為work count標識當前工作執行緒的數量

上述傳入的是ctlOf(RUNNING, 0) ,當前狀態為RUNING也就是1110 0000 0000 0000 0000 0000 0000 0000 ,wc為0,也就是當前工作執行緒數為0,其二進位制為0000 0000 0000 0000 0000 0000 0000 0000 ,做"|"或操作,即

1110 0000 0000 0000 0000 0000 0000 0000 | 0000 0000 0000 0000 0000 0000 0000 0000
= 1110 0000 0000 0000 0000 0000 0000 0000

上述得到的結果1110 0000 0000 0000 0000 0000 0000 0000就標識,當前執行緒池狀態為RUNNING,執行緒池活躍執行緒個數為0!

如何管理?

通過上述建立的ctl變數獲取 執行緒池當前狀態 和 執行緒中活躍執行緒個數 這兩個狀態:

獲取執行緒池當前狀態,我們可以想一下該如何獲取呢? 現在知道的是ctl的前3位是執行緒池的狀態,那我們直接構造一個前三位為1,後29位為0的int即可,然後取餘就可以了唄,下面看下原始碼的實現,就是如此:
使用方法runStateOf

 private static int runStateOf(int c)     { return c & ~CAPACITY; }

其中CAPACITY = (1 << COUNT_BITS) - 1 轉化為二進位制為:
0001 1111 1111 1111 1111 1111 1111 1111
取反"~"後,二進位制為:
1110 0000 0000 0000 0000 0000 0000 0000
也就是將前3位全部變為1,後面全部變為0;
接下來,傳入的ctl變數和~CAPACITY做“&”操作,只會保留ctl變數的前3位變數,後29位變數全部為0;

例如:一個標識當前狀態為STOP狀態的執行緒池和當前活躍執行緒數為3的ctl變數為:
0010 0000 0000 0000 0000 0000 0000 0011
和上述得到的1110 0000 0000 0000 0000 0000 0000 0000做“&”操作後得到:
0010 0000 0000 0000 0000 0000 0000 0000 和上述分析的STOP的狀態的二進位制相同! 即獲得了當前執行緒的狀態!

獲取執行緒池當前狀態,也很簡單,我們知道ctl變數的32的後29位儲存的是當前活躍執行緒數,直接構造一個前三位為0,後29位為1的int即可,然後取餘就可以獲取到了
使用方法workerCountOf

private static int workerCountOf(int c)  { return c & CAPACITY; }

上述知道CAPACITY為:0001 1111 1111 1111 1111 1111 1111 1111

例如:一個標識當前狀態為STOP狀態的執行緒池和當前活躍執行緒數為3的ctl變數為:
0010 0000 0000 0000 0000 0000 0000 00110001 1111 1111 1111 1111 1111 1111 1111 取與後:
0000 0000 0000 0000 0000 0000 0000 0011
標識當前執行緒池中活躍執行緒數量為3!

一些方法

1、計算ctl的值

方法:

private static int ctlOf(int rs, int wc) { return rs | wc; }

其中,入參rs代表當前執行緒狀態,wc代表當前活躍執行緒數,取“|”或即可
上述程式碼不出現問題的前提是:rs只使用的前3位,wc只使用了後29位!

2、判斷當前執行緒池是否正在執行

方法:

private static boolean isRunning(int c) {  return c <小於SHUTDOWN;}值即可!

上述我們知道,5中狀態只有RUNNING小於0,SHUTDOWN狀態等於0,其他的都是大於0的,所以我們直接把給定的ctl值小於SHUTDOWN值即可!

最後

上述,我們介紹了 執行緒池的狀態 管理部分,主要通過不同位置的二進位制來進行標識不同的狀態,工作學習還會發現更多巧妙美妙的設計,等待著作為程式設計師我們去發現;