Java高併發程式設計之synchronized關鍵字(二)
阿新 • • 發佈:2018-11-16
上一篇文章講了synchronized的部分關鍵要點,詳見:Java高併發程式設計之synchronized關鍵字(一)
本篇文章接著講synchronized的其他關鍵點。
在使用synchronized關鍵字的時候,不要以字串常量作為鎖定物件。看下面的例子:
public class T012 { public String s1 = "hello"; public String s2 = "hello"; public void m1() { synchronized (s1) { System.out.println("m1 start..."); try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("m1 end."); } } public void m2() { synchronized (s2) { System.out.println("m2 start..."); System.out.println("m2 end."); } } public static void main(String[] args) { T012 t012 = new T012(); new Thread(t012::m1, "Thread_1").start(); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } new Thread(t012::m2, "Thread_2").start(); } }
thread_1執行m1方法,thread_2執行m2方法,然而m2方法卻要等到m1執行結束才開始,這說明,m1和m2其實鎖定的是同一個物件。這種情況還會發生比較詭異的現象,比如你用到了一個類庫,在該類庫中程式碼鎖定了字串“hello”,但是因為你讀不到原始碼,所以你在自己的程式碼中也鎖定了“hello”,這時候就有可能發生非常詭異的死鎖阻塞,因為你的程式和你的類庫不經意間使用了同一把鎖。
鎖定某個物件o,如果o的屬性發生改變,不影響鎖的使用。但是如果o變成了另一個物件,則鎖定的物件發生改變,應該避免 將鎖定物件的應用變成另外的物件。
public class T013 { Object o = new Object(); void m() { synchronized (o) { while (true) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { T013 t013 = new T013(); new Thread(t013::m, "thread_1").start(); try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } Thread t2 = new Thread(t013::m, "thread_2"); //鎖物件發生改變 t013.o = new Object(); t2.start(); } }
上面的程式碼中,如果物件o所引用的物件不發生改變,t2執行緒是不會執行的,然而執行程式可發現t2執行緒也運行了,這就證明,synchronized鎖定的是堆記憶體中的物件,而不是物件的應用,所以要避免改變鎖定的物件。
如何對synchronized進行優化呢?同步程式碼塊中的語句越少越好!
觀察下面程式:
public class T014 { int count = 0; synchronized void m1() { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } count++; try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } } void m2() { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } //業務邏輯中只有count++需要sync,這時不應該給整個方法上鎖 //採用細粒度的鎖,可以使執行緒爭用時間變短,從而提高效率 synchronized (this) { count++; } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } } }
上述程式碼中,m2方法的效率,要遠高於m1方法(具體可以自行測試下)