跟我學程式碼架構設計模式之--鎖和執行緒的補充
本文講講對理解鎖和執行緒有幫助的一些零散的點~
# 再思考執行緒的本質
首先理解一點:執行緒會阻塞,CPU永遠不會阻塞,除非電腦休眠!CPU一直在迴圈的忙碌執行指令,不會阻塞!
在CPU的角度上看,執行緒其實就是一個個的資料物件!這個資料物件包含了CPU要執行的程式碼的CPU暫存器(包括堆疊位置相關的暫存器)資訊,也就是執行緒上下文資訊,另外還應該包含執行緒相關的一些狀態資料,比如優先順序和執行緒狀態等!我們的執行緒會阻塞和恢復執行,完全是因為CPU永遠不會休息!
執行緒阻塞了是因為CPU當前時段不執行這個執行緒上下文了,而是選擇了其他上下文去執行;執行緒又恢復執行了是因為CPU又把該執行緒的上下文切換回來繼續執行了。
本質上,我們的執行緒的排程都是由於“上帝之手”的存在---永不停歇運轉的CPU和硬體定時器的持續運轉!
CPU說:執行哪個執行緒的上下文程式碼都一樣,我一直是順序執行指令的的,對於我來說我只是“跳著”執行記憶體不同地方的程式碼讓你們誤認為我是並行的而已,你們感受到並行都是我虛構出的~
# 設計的角度分析下鎖
對於一個多執行緒併發環境下有關聯資源的函式,在函式程式碼編寫角度上,給函式加鎖實際上相當於面向介面程式設計AOP理念--即鎖可以單獨被實現,然後被介入到各種函式的執行過程中去!說白了,按照面向物件的思想,鎖可以單獨實現為各種類!鎖這些個類相當於把“資源保護”這個公共的邏輯抽出來,這樣我們編寫需要資源保護的函式的時候,直接呼叫鎖相關的操作就可以了~
# 面向物件的角度分析鎖物件應該如何設計
首先,我們先進行簡單需求分析:
首先 鎖是為了保護資源訪問而協調執行緒執行順序,所以應該有狀態資料來確保當前允許和禁止哪些執行緒執行;
其次,鎖(自旋鎖應該除外)應該還能阻塞得不到鎖狀態的執行緒,所以應該有個阻塞佇列,得不到鎖的執行緒物件應該被放進阻塞佇列中等待;
上面說的狀態和阻塞佇列是鎖物件的資料,是多個執行緒共享的資料。此外鎖還應該提供如下的操作方法:
1 獲得鎖方法,方法實現為:原子的判斷鎖狀態(因為鎖資料本身也是共享的,需要保護),如果當前執行緒獲取不到鎖,應該把該執行緒設定為阻塞狀態,並放入鎖的阻塞佇列中等待。
2 解鎖方法:獲得鎖的執行緒原子的修改鎖狀態資料來釋放鎖,同時獲取鎖的阻塞佇列,通知一個執行緒可以執行(即修改執行緒物件的狀態為執行態,讓CPU可以排程到這個執行緒)。
補充:由於鎖本身的狀態也是多執行緒共享的資料,也需要保護,一般是通過原子的CAS操作,即compare and set 、compare and get、compare and swap,分別對應同步的設定狀態資料、獲取狀態資料和修改狀態資料,有了這幾個操作作為根本保證,才有了鎖可以被單獨實現為公共操作類的理論基礎~
總結:
1 鎖物件應該是多執行緒共享的
2 鎖物件應該有狀態資料
3 鎖物件應該有阻塞佇列
4 CAS操作確保了鎖本身狀態的同步修改
(完)