一次同步方法的失敗與原因總結
阿新 • • 發佈:2018-12-26
synchronized關鍵字可以設定同步方法和同步塊,同步方法會對呼叫物件this上鎖,
所有執行緒進入前都需要獲取這個鎖,這裡我拿一個錯誤樣例來做示範
1 public class Test { 2 public static void main(String[] args) { 3 Account account=new Account(100); 4 Person p1=new Person(account,80); 5 Person p2=new Person(account,90); 6 p1.start();7 p2.start(); 8 } 9 } 10 11 class Account{ 12 int total; 13 public Account(int total) { 14 super(); 15 this.total=total; 16 } 17 } 18 19 class Person extends Thread{ 20 private int reduce; 21 private Account account; 22 publicPerson(Account account,int reduce) { 23 24 this.account=account; 25 this.reduce=reduce; 26 } 27 28 public synchronized void run() { 29 if (account.total-reduce<0) return ; 30 try { 31 Thread.sleep(200); 32 } catch (InterruptedException e) {33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 account.total-=reduce; 37 System.out.println(Thread.currentThread().getName()+"取出"+reduce); 38 System.out.println(Thread.currentThread().getName()+"剩餘"+account.total); 39 40 } 41 }
Thread-0取出80
Thread-0剩餘-70
Thread-1取出90
Thread-1剩餘-70
出現了負數,很明顯沒鎖住,現在我們來分析下原因。
有1個account物件,兩個person物件p1和p2,p1和p2爭搶資源account
我一開始的設想是,當p1進入方法時,p1物件上鎖,account作為p1的成員也被上鎖,此時p2進入就需要等到p1釋放鎖,由此達到我們的目標。
問題在於:java規定每個物件都有一個監視器,使用時檢視上鎖了沒,上面的程式碼中雖然對person物件上了鎖,同時account作為物件也有監視器,account雖然作為person的成員,但account的監視器是單獨的,不受p1的鎖影響。現在我們再來場景復原一下,p1先進入方法,對p1上鎖,計算100-80=20,同時p2進入方法,檢視account的監視器,沒有上鎖,計算20-90=-70,這就產生了錯誤。
解決方法就是使用同步塊給account上鎖
1 public void run() { 2 synchronized(account) { 3 if (account.total-reduce<0) return ; 4 try { 5 Thread.sleep(200); 6 } catch (InterruptedException e) { 7 // TODO Auto-generated catch block 8 e.printStackTrace(); 9 } 10 account.total-=reduce; 11 System.out.println(Thread.currentThread().getName()+"取出"+reduce); 12 System.out.println(Thread.currentThread().getName()+"剩餘"+account.total); 13 14 } 15 }
這個問題給我們的教訓在於,每個物件都有單獨的監視器,要選取正確的爭奪物件上鎖,基於此同步塊不僅能縮小鎖的粒度還能選取正確的物件上鎖。