如何實現程序支持異步:java
class X{ //修飾非靜態方法 synchronized void foo(){ //臨界區 } //修飾靜態方法 synchronized static void bar(){ //臨界區 } //修飾代碼塊 Object obj = new Object(); void baz(){ synchronized(obj){ //臨界區 } } }
Java編譯器會在synchronized修飾的方法或代碼塊先後自動加上加鎖lock()和解鎖unlock(),這樣作的好處就是加鎖lock()和解鎖unlock()必定 是成對出現的,畢竟忘記解鎖unlock()但是個致命的Bug(意味着其餘線程只能死等下去了)。安全
//修飾靜態方法是用當前類的字節碼文件做爲鎖 class X{ //修飾靜態方法 synchronized(X.class) static void bar(){ //臨界區 } }
//修飾非靜態方法是用當前對象做爲鎖 class X{ //修飾非靜態方法 synchronized(this) static void bar(){ //臨界區 } }
如何用一把鎖保護多個資源性能優化
受保護資源和鎖之間合理的關聯關係應該是N:1的關係,也就是說能夠用一把鎖來保護多個資源,可是不能用多把鎖來保護一個資源,併發
示例一:異步
public class Account { /** *鎖:保護帳⼾餘額 */ private final Object balLock = new Object(); /** * 帳⼾餘額 */ private Integer balance; /** * 錯誤的作法 * 非靜態方法的鎖是this, * this這把鎖能夠保護本身的餘額this.balance,保護不了別人的餘額 target.balance * */ synchronized void transfer(Account target,int amt){ if (this.balance > amt) { this.balance -= amt; target.balance += amt;//這段代碼會出現線程安全,要保證線程安全的話要使用同一個鎖 } } }
示例二:性能
public class Account { /** *鎖:保護帳⼾餘額 */ private final Object balLock = new Object(); /** * 帳⼾餘額 */ private Integer balance; /** * 正確的作法,可是會致使整個轉帳系統的串行 * * Account.class是全部Account對象共享的, * 並且這個對象是Java虛擬機在加載Account類的時候建立的, * 因此咱們不用擔憂它的惟一性 * * 這樣還有個弊端:全部的轉帳都是串行了 */ void transfer2(Account target,int amt){ synchronized(Account.class){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
這樣的話轉帳操做就成了串行的了,正常的邏輯應該只鎖轉入帳號和被轉入帳戶;不影響其餘的轉帳操做。稍做改造:優化
示例三:this
public class Account { /** *鎖:保護帳⼾餘額 */ private final Object lock; /** * 帳⼾餘額 */ private Integer balance; //私有化無參構造 private Account(){} //設置一個傳遞lock的有參構造 private Account(Object lock){ this.lock = lock; } /** * 轉帳 */ void transfer(Account target,int amt){ //此處檢查全部對象共享鎖 synchronized(lock){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
這個方法雖然可以解決問題,可是它要求建立Account對象的時候必須傳入同一個對象,線程
還有就是傳遞對象過於麻煩,寫法繁瑣缺少可行性。code
示例四:
public class Account { /** * 帳⼾餘額 */ private Integer balance; /** * 轉帳 */ void transfer(Account target,int amt){ //此處檢查全部對象共享鎖 synchronized(Account.class){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } }
用Account.class做爲共享的鎖,鎖定的範圍太大。 Account.class是全部Account對象共享的,並且這個對象是Java虛擬機在加載Account類的時候建立的,因此咱們不用擔憂它的惟一性。使用Account.class做爲共享的鎖,咱們就無需在建立Account對象時傳入了。
這樣新的問題就出來了雖然用Account.class做爲互斥鎖,來解決銀行業務裏面的轉帳問題,雖然這個方案不存在 併發問題,可是全部帳戶的轉帳操做都是串行的,例如帳戶A轉帳戶B、帳戶C轉帳戶D這兩個轉帳操做現實 世界裏是能夠並行的,可是在這個方案裏卻被串行化了,這樣的話,性能太差。因此若是考慮併發量這種方法也不行的
正確的寫法是這樣的(使用細粒度鎖):
示例五:
public class Account { /** * 帳⼾餘額 */ private Integer balance; /** * 轉帳 */ void transfer(Account target,int amt){ //鎖定轉出帳戶 synchronized(this){ //鎖住轉入帳戶 synchronized(target){ if (this.balance > amt) { this.balance -= amt; target.balance += amt; } } } } }
咱們試想在古代,沒有信息化,帳戶的存在形式真的就是一個帳本,並且每一個帳戶都有一個帳本,這些帳本 都統一存放在文件架上。銀行櫃員在給咱們作**轉帳時,要去文件架上把轉出帳本和轉入帳本都拿到手,而後作轉帳。**這個櫃員在拿帳本的時候可能遇到如下三種狀況:
- 文件架上剛好有轉出帳本和轉入帳本,那就同時拿走;
- 若是文件架上只有轉出帳本和轉入帳本之一,那這個櫃員就先把文件架上有的帳本拿到手,同時等着其 他櫃員把另一個帳本送回來;
- 轉出帳本和轉入帳本都沒有,那這個櫃員就等着兩個帳本都被送回來。
若是有客戶找櫃員張三作個轉帳業務:帳戶 A轉帳戶B 100元,此時另外一個客戶找櫃員李四也作個轉帳業務:帳戶B轉帳戶A 100元,因而張三和李四同時都去文件架上拿帳本,這時候有可能湊巧張三拿到了帳本A,李四拿到了帳本B。張三拿到帳本A後就等着 帳本B(帳本B已經被李四拿走),而李四拿到帳本B後就等着帳本A(帳本A已經被張三拿走),他們要等 多久呢?他們會永遠等待下去…由於張三不會把帳本A送回去,李四也不會把帳本B送回去。咱們姑且稱爲死等吧。
只要破壞其中一個就能夠避免死鎖
用synchronized實現等待-通知機制
- synchronized 配合wait(),notif(),notifyAll()這三個方法可以輕鬆實現.
- wait(): 當前線程釋放鎖,進入阻塞狀態
- notif(),notifAll(): 通知阻塞的線程有能夠繼續執行,線程進入可執行狀態
- notif()是會隨機地地通知等待隊歹一個線程
- notifyAll()會通知等待隊列中的全部線程,建議使用notifAll()
wait與sleep區別:
sleep是Object的中的方法,wait是Thread中的方法
wait會釋放鎖,sleep不會釋放鎖
wait須要用notif喚醒,sleep設置時間,時間到了喚醒
wait無需捕獲異常,而sleep須要
wait(): 當前線程進入阻塞
**** 碼字不易若是對你有幫助請給個關注****
**** 愛技術愛生活 QQ羣: 894109590****