Synchronized關鍵字淺析
總結的話寫在前面:
Synchronized(A){
//程式碼塊
。。。。。
}
A可以表示普通成員變數,靜態成員變數,類例項,類等。
持有A所在的記憶體區域的鎖時才能執行該段程式碼塊。
所以對於多個執行緒執行的同步程式碼塊是否需要獲取同一把鎖,關鍵是看Synchronized修飾詞A所指向的是否為同一個記憶體地址。
來看程式碼:
public class Count { int num = 200; public void count_num() { this.num--; } public static void main(String args[]) { Count c = new Count(); ThreadNum t1 = new ThreadNum(c); ThreadNum t2 = new ThreadNum(c); ThreadNum t3 = new ThreadNum(c); ThreadNum t4 = new ThreadNum(c); t1.start(); t2.start(); t3.start(); t4.start(); } } class ThreadNum extends Thread { Count t_; String name; public ThreadNum(Count t) { this.t_ = t; } public void run() { while (true) { synchronized (t_) { this.name = Thread.currentThread().getName(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(t_.num > 0){ t_.count_num(); }else{ break; } System.out.println(this.name + " " + t_.num); } } } }
t1,t2,t3,t4四個執行緒的同步程式碼塊共用的時同一個物件例項c的鎖,記憶體中都指向同一個Count引用,所以實現了count有序遞減。
再來看Synchronized修飾類成員變數:
public class ThreadTestffd { private Integer a = 0; private Integer b = 0; public static void main(String[] args) { ThreadTestffd test = new ThreadTestffd(); test.A(); test.B(); } public void A() { new Thread(new Runnable() { @Override public void run() { System.out.println("A執行緒準備"); synchronized (a) { System.out.println("A執行緒開始"); for (int i = 0; i < 10; i++) { System.out.println("a" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } }).start(); } public void B() { new Thread(new Runnable() { @Override public void run() { System.out.println("B執行緒準備"); synchronized (b) { System.out.println("B執行緒開始"); for (int i = 0; i < 10; i++) { System.out.println("b" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } }).start(); } }
A方法Synchronized修飾成員變數a,B方法修飾成員變數b,因為a和b都是同一個物件(Integer)且記憶體中指向的地址一致(test.a==test.b)為true,故最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。
如果把程式碼改成
private Integer a = 0; private Integer b = 0; public static void main(String[] args) { ThreadTestffd test = new ThreadTestffd(); test.A(); ThreadTestffd test2 = new ThreadTestffd(); test2.B(); }
同樣的分析過程:
(test.a==test2.b)為true,故最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。
如果把程式碼改成
private Integer a = 0;
private Integer b = 1;
public static void main(String[] args) {
ThreadTestffd test = new ThreadTestffd();
test.A();
test.B();
}
那麼,a和b記憶體地址指向不一致(test.a==test.b)為false,故最後的結果是a和b執行緒交替列印。
程式碼改成
private Person a=new Person();
private Person b=new Person();
public static void main(String[] args) {
ThreadTestffd test = new ThreadTestffd();
test.A();
test.B();
}
那麼,a和b記憶體地址指向兩個不同的Person引用(test.a==test.b)為false,故最後的結果是a和b執行緒交替列印。
程式碼改成
private Integer[] a = new Integer[]{0,1};
private Integer[] b = new Integer[]{2,1};
public static void main(String[] args) {
ThreadTestffd test = new ThreadTestffd();
test.A();
test.B();
System.out.println(test.a[1]==test.b[1]);
}
且方法A和方法B中的Synchronized關鍵字修飾變數由a,b分別改為a[1],b[1],因為(test.a[1]==test.b[1])為true,所以a,b雖然是不同的陣列,但在a[1]和b[1]記憶體中指向的地址是一致的,所以最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。
同理,不難理解Synchronized修飾靜態方法(public static synchronized void test(){})和程式碼塊Synchronized(A.Class){
}
都是指向記憶體中方法區內某個類的鎖,所以對於同一個類來說,他們是同一把鎖