從分佈式鎖角度理解Java的synchronized關鍵字

分佈式鎖

分佈式鎖就以zookeeper爲例,zookeeper是一個分佈式系統的協調器,咱們將其理解爲一個文件系統,能夠在zookeeper服務器中建立或刪除文件夾或文件.設D爲一個數據系統,不具有事務能力,在併發狀態下可能出現對單個數據同時讀寫.客戶端A,B是數據系統D提供的客戶端,可以對其讀寫.java

幾個關鍵角色已經登場,D是一個不提供事務行爲的數據系統,其存放的數據可被讀寫,在單客戶端條件下能夠保證數據的可靠,可是在兩個客戶端可能併發請求時就變得不可靠,A寫的數據可能被B覆蓋,B讀的數據多是A沒有寫完的數據.在不修改D源碼爲其提供原子性操做的前提下,咱們能夠考慮分佈式鎖.服務器

客戶端的原始API併發

void write(){
    // 寫數據
}
void read(){
    // 讀數據
}

如何作呢,核心就是以zookeeper爲媒介.分佈式

修改客戶端API,若是讀或者寫,咱們首先要在zookeeper上建立一個文件夾,若是建立成功,咱們就執行讀寫操做,若是不成功(表明已經有人建立了)咱們就一直嘗試建立,直到建立成功.當建立成功時,對D系統的數據進行讀寫操做,完成後刪除zookeeper上建立的文件夾並退出讀寫方法.this

void write(){
 while(在zookeeper上建立文件夾) {
     寫操做;
     刪除zookeeper的剛建立的文件夾;
     return;
 }   
}

這樣就完成了一個分佈式併發行爲的同步.假設A已經建立了文件夾,B就沒辦法進行後續操做,會一直嘗試建立文件夾,A執行完操做以後刪除文件夾,B纔有機會進行在D系統上的操做.這個例子中產生了一個臨界資源:D系統的數據,和一個競態條件:A和B併發讀寫.(實際上zookeeper中是能夠爲監聽者發送文件狀況的,好比我建立文件夾沒有成功,能夠監聽該文件夾,當文件夾變化時會通知你,這時候你就能夠再嘗試建立文件夾了(很像wait和notify),可是爲了避免把重心放在zookeeper上就沒有改爲監聽模式)線程

Java中Synchronized關鍵字

那咱們再回到Java中的synchronized關鍵字上來,若是你還沒理解上面的行爲和synchronized的關係你能夠繼續日後看.code

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對象的鎖標誌位,若是觀察實例對象的鎖標誌位會竹籃打水喲.

相關文章
相關標籤/搜索