Synchronized鎖的是什麼?

2022-01-18 04:04:40 字數 3403 閱讀 7287

併發程式設計中不可避免的會出現多個執行緒共享同乙個資源的情況,為了防止出現資料不一致情況的發生,人們引入了臨界區的概念。臨界區是乙個用來訪問共享資源的**塊,同一時間內只執行乙個執行緒進入。

那麼如何實現這個臨界區呢?這就用到我們的鎖了,當程序想要訪問乙個臨界區時,它先會去看看是否已經有其他執行緒進入了,也就是看是否能獲得鎖。如果沒有其他執行緒進入,那麼它就進入臨界區,其他執行緒就無法進入,相當於加鎖。反之,則會被掛起,處於等待狀態,直到其他執行緒離開臨界區,且本執行緒被jvm選中才可進入(因為可能有其他執行緒也在等待)。

synchronize是乙個重量級鎖,它會降低程式效能,因此如果對資料一致性沒有要求,就不要使用它。如果方法被synchronize關鍵字宣告,那麼該方法的**塊被視為臨界區。當某個執行緒呼叫該物件的synchronized方法或者訪問synchronized**塊時,這個執行緒便獲得了該物件的鎖,其他執行緒暫時無法訪問這個方法,只有等待這個方法執行完畢或者**塊執行完畢,這個執行緒才會釋放該物件的鎖,其他執行緒才能執行這個方法或者**塊。

下面我們將建立兩個執行緒a,b來同時訪問乙個物件:a從賬戶裡取錢,b從賬戶裡存錢。首先是不使用synchronized關鍵字。

建立賬戶類

它擁有乙個私有變數balance表示金額,addamount和subtractamount分別對金額執行加減操作。

public class account 

public void setbalance(double balance)

public void addamount(double amount) catch (interruptedexception e)

temp+=amount;

balance=temp;

system.out.println("addamount end");

}public void subtractamount(double amount) catch (interruptedexception e)

temp-=amount;

balance=temp;

system.out.println("subtractamount end");}}

建立a,b倆執行緒,分別對賬戶存錢和取錢。

public class a implements runnable 

@override

public void run() }}

public class b implements runnable  

@override

public void run() }}

最後在main裡面測試

public class main  catch (interruptedexception e) }}

threada往賬戶中執行了10次存入操作,每次存入1000元,threadb則是以同樣的金額執行了10次取出操作。那麼按照我們的推測,最後賬戶的金額應該維持不變,但程式的結果卻不是我們想要的數字。這是為什麼呢?因為我們在對資料進行操作的時候,另外乙個執行緒可能也在進行操作,邏輯上應該先後執行的方法變成了同時執行,所以出現了錯誤。

現在我們給addamount和subtractamount加上synchronized關鍵字,保證資料一致性,這樣程式就不會出問題了。

如果是使用synchronize保護**塊,則需要將物件引用作為引數傳入。一般來說傳入this關鍵字作為引用執行方法的物件就可以了。

或許在上面的例子你因為粗心只為其中乙個方法加了關鍵字,那麼你會看到這樣的現象:

保護**塊要將物件傳入,那應該鎖的是物件呀。你可能會想:我執行subtractamout,按道理應該等我執行完addamount才能執行,它都沒有account這個物件的鎖,不應該在中間插這麼一段呀。但是,只有加了鎖的方法,執行緒執行該方法時才會去嘗試獲得鎖,看看是否有執行緒進入臨界區。訪問非同步方法無需獲得鎖,你把synchronized去掉跟你只加乙個的情況是一樣的,同步方法與非同步遵循的是不同的規則。也就是說你可以在呼叫該物件的加了synchronized方法的同時,呼叫其他的非同步方法。

你可能在搗鼓這個關鍵字的時候,驚訝的發現靜態方法的與眾不同。如果乙個物件中的靜態方法用synchronized修飾,那麼其他執行緒可以在該靜態方法被訪問的同時,訪問該物件中的非靜態方法(當然,該靜態方法同一時間只能被乙個執行緒訪問)。換句話說,兩個執行緒可以同時訪問乙個物件中的兩個synchronized方法。

等等,不是說鎖物件嗎?到底鎖的是什麼?鎖的確實是物件,但對於靜態方法我們說的是t.class(t 為類名),非靜態方法鎖的是this ,也就是類的例項物件,兩者是不同的。

class t 

// 修飾靜態方法

public synchronized static void b()

}

上面那段**相當於:

class t 

// 修飾靜態方法

public synchronized(t.class) static void b()

}

實際上加鎖本質就是在鎖物件的物件頭中寫入當前執行緒id。我們可以通過下面的**驗證,每次都傳入new object()。

class account  catch (interruptedexception e) 

temp+=amount;

balance=temp;

system.out.println("addamount end");}}

public void subtractamount(double amount) catch (interruptedexception e)

temp-=amount;

balance=temp;

system.out.println("subtractamount end");}}

}

因為執行緒每次呼叫方法鎖的都是新new的物件,所以加鎖無效。甚至編譯器可能會將synchronized給優化掉,因為這相當於多把鎖保護同乙個資源,編譯器一看,每個人都弄把鎖就進來了,那我還不如不加,反正都乙個樣。

另外需要注意的是,synchronized是可重入鎖。也就是說當執行緒訪問物件的同步方法時,在呼叫其他同步方法時無需再去獲取其訪問權。因為我們實際上鎖的是物件,物件頭裡面紀錄的都是當前執行緒的id。

加鎖實際上就是在鎖物件的物件頭中寫入當前執行緒id,每個執行緒要想呼叫這個同步方法,都會先去鎖物件的物件頭看看當前執行緒id是不是自己的。

參考​ synchronized鎖定的到底是什麼?-知乎

synchronized鎖的公升級原理是什麼

鎖的級別從低到高 無鎖 偏向鎖 輕量級鎖 重量級鎖 鎖分級別原因 沒有優化以前,sychronized是重量級鎖 悲觀鎖 使用 wait 和 notify notifyall 來切換執行緒狀態非常消耗系統資源 執行緒的掛起和喚醒間隔很短暫,這樣很浪費資源,影響效能。所以 jvm 對 sychroni...

synchronized 鎖的公升級原理是什麼

鎖的級別從低到高 無鎖 偏向鎖 輕量級鎖 重量級鎖 鎖分級別原因 沒有優化以前,sychronized是重量級鎖 悲觀鎖 使用 wait 和 notify notifyall 來切換執行緒狀態非常消耗系統資源 執行緒的掛起和喚醒間隔很短暫,這樣很浪費資源,影響效能。所以 jvm 對 sychroni...

synchronized 鎖的重入

1 乙個同步方法呼叫另外乙個同步方法,能否得到鎖?重入 synchronized預設支援重入 slf4j topic test public class demo catch interruptedexception e test2 為什麼test2還需要加sync 他本身就包含在test1 而te...