分布式鎖
分布式鎖就以zookeeper
為例,zookeeper
是一個分布式系統的協調器,我們將其理解為一個文件系統,可以在zookeeper
服務器中創建或刪除文件夾或文件.設D為一個數據系統,不具備事務能力,在並發狀態下可能出現對單個數據同時讀寫.客戶端A,B是數據系統D提供的客戶端,能夠對其讀寫.
幾個關鍵角色已經登場,D是一個不提供事務行為的數據系統,其存放的數據可被讀寫,在單客戶端條件下可以保證數據的可靠,但是在兩個客戶端可能並發請求時就變得不可靠,A寫的數據可能被B覆蓋,B讀的數據可能是A沒有寫完的數據.在不修改D源碼為其提供原子性操作的前提下,我們可以考慮分布式鎖.
客戶端的原始API
void write(){
// 寫數據
}
void read(){
// 讀數據
}
如何做呢,核心就是以zookeeper
為媒介.
修改客戶端API,如果讀或者寫,我們首先要在zookeeper
上創建一個文件夾,如果創建成功,我們就執行讀寫操作,如果不成功(代表已經有人創建了)我們就一直嘗試創建,直到創建成功.當創建成功時,對D系統的數據進行讀寫操作,完成后刪除zookeeper
上創建的文件夾並退出讀寫方法.
void write(){
while(在zookeeper上創建文件夾) {
寫操作;
刪除zookeeper的剛創建的文件夾;
return;
}
}
這樣就完成了一個分布式並發行為的同步.假設A已經創建了文件夾,B就沒辦法進行后續操作,會一直嘗試創建文件夾,A執行完操作之后刪除文件夾,B才有機會進行在D系統上的操作.這個例子中產生了一個臨界資源:D系統的數據,和一個競態條件:A和B並發讀寫.(實際上zookeeper
中是可以為監聽者發送文件情況的,比如我創建文件夾沒有成功,可以監聽該文件夾,當文件夾變化時會通知你,這時候你就可以再嘗試創建文件夾了(很像wait和notify),但是為了不把重心放在zookeeper上就沒有改成監聽模式)
Java中Synchronized關鍵字
那我們再回到Java
中的synchronized
關鍵字上來,如果你還沒理解上面的行為和synchronized
的關系你可以繼續往后看.
synchronized
究竟鎖住的是什么呢,鎖住的是一塊內存,我們在內存中的某個位置設置一個值為0,當該值為0時,一個線程為了修改臨界資源將其設置為1,然后讀寫操作,讀寫結束將其設置為0,其他線程可以再次將其改為1,進行讀寫,結束后再將其置為0......這塊內存存儲在每個對象的對象頭中,我們稱其為鎖標志位(實際上所標志位不是簡單的0和1,鎖標志位分為四種狀態:無鎖,偏向鎖,輕量級鎖,和重量級鎖,並發中還涉及鎖升級等,但本文不做細究,有興趣的讀者可以查閱相關資料).
當進入synchronized
代碼塊或者方法的時候,你會像上面說的分布式鎖那樣"創建一個文件夾",其他線程無法"創建文件夾"就會一直去嘗試,當代碼塊或方法結束的時候會"刪除文件夾",其他線程可以去搶奪創建文件夾的機會.synchronized
可以看做一道門,鎖住的對象可以看做是門票.千萬不要認為synchronized
鎖住的是臨界資源,synchronized
是以某個對象的鎖標志作為入場券去約束競態線程之間行為:我只有一張門票,我只給一個人.
看一下synchronized
的用法,多個線程之間的競態行為一定要是用相同的門票去約束.
// 這鎖住的是
class Foo{
// 這鎖住的是類對象
static synchronized void bar();
// 這鎖住的是this,即實例對象
synchronized void bar();
// 等價於static synchronized
void bar(){
synchronized(Foo.class){};
}
// 等價於synchronized實例方法
void bar(){
synchronized(this){}
}
}
對於靜態方法需要注意一點(假如你想觀察偏向鎖的變化),實例方法可以直接觀察實例對象的對象頭上的鎖標志位,但是靜態方法需要觀察Foo.class對象的鎖標志位,如果觀察實例對象的鎖標志位會竹籃打水喲.