本篇隨筆主要介紹 java 中 synchronized 關鍵字常用法,主要有以下四個方面:
1、實例方法同步
2、靜態方法同步
3、實例方法中同步塊
4、靜態方法中同步塊
我覺得在學習synchronized關鍵字之前,我們首先需要知道以下一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的。
一、實例方法中使用 synchronized 加鎖
實例方法中默認被加鎖的對象是調用此方法的實例對象。
1 class ImmutableValue {
2 public synchronized void comeIn() throws InterruptedException{
3 System.out.println(Thread.currentThread().getName() + ": start");
4 Thread.sleep(5000);
5 System.out.println(Thread.currentThread().getName() + ": finish");
6 }
7 public void synchronized comeInIn() throws InterruptedException {
8 System.out.println(Thread.currentThread().getName() + ": start");
9 Thread.sleep(5000);
10 System.out.println(Thread.currentThread().getName() + ": finish");
11 }
12 }
13 public class TestImmutableValue {
14 public static void main(String[] args) {
15 ImmutableValue im = new ImmutableValue();
16 Thread t1 = new Thread(new Runnable() {
17
18 @Override
19 public void run() {
20 // TODO Auto-generated method stub
21 try {
22 im.comeIn();
23 } catch (InterruptedException e) {
24 // TODO Auto-generated catch block
25 e.printStackTrace();
26 }
27 }
28
29 }, "t1");
30 Thread t2 = new Thread(new Runnable() {
31
32 @Override
33 public void run() {
34 // TODO Auto-generated method stub
35 try {
36 im.comeInIn();
37 } catch (InterruptedException e) {
38 // TODO Auto-generated catch block
39 e.printStackTrace();
40 }
41 }
42
43 }, "t2");
44 t1.start();
45 t2.start();
46 }
47 }
在上面的代碼中創建了兩個線程並分別命名為 t1, t2。調用了同一個對象 im 的兩個同步方法 comeIn 和 comeInIn, 執行結果如下:

在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 comeInIn 方法仍然沒有得到執行。這是因為 t1 線程先執行的 comeIn 方法,持有了對象 im 的鎖,且 comeIn 方法並沒有執行完,對象 im 的鎖沒有被釋放,所以 comeInIn 方法無法對對象 im 加鎖,就無法繼續執行,只能等到 t1 線程中的 comeIn 方法執行完畢,釋放對象 im 的鎖,comeInIn 方法才能繼續執行。
但是如果 t1 線程調用的是對象 im 的 comeIn 方法,而 t2 線程調用的是我們聲明的另外一個 ImmutableValue 對象 im2 對象的 comeInIn 方法,則這兩個方法的執行是互不影響的。因為 t1 線程的 comeIn 方法要獲得 im 對象的鎖,而 t2 線程要獲得的是 im2 對象的鎖,兩個鎖並不是同一個鎖(Java中每個實例對象都有且只有一個鎖),所以這兩個方法執行互不影響。
二、靜態方法中使用 synchronized 加鎖
靜態方法中默認被加鎖的對象是此靜態方法所在類的 class 對象。
1 class staticMethodSynchronized {
2 public static synchronized void method1() throws InterruptedException {
3 System.out.println(Thread.currentThread().getName() + ": start");
4 Thread.sleep(5000);
5 System.out.println(Thread.currentThread().getName() + ": finish");
6 }
7 public static synchronized void method2() throws InterruptedException {
8 System.out.println(Thread.currentThread().getName() + ": start");
9 Thread.sleep(5000);
10 System.out.println(Thread.currentThread().getName() + ": finish");
11 }
12 }
13 public class TestStaticClassSynchronized {
14 public static void main(String[] args) {
15 Thread t1 = new Thread(new Runnable() {
16
17 @Override
18 public void run() {
19 // TODO Auto-generated method stub
20 try {
21 staticMethodSynchronized.method1();
22 } catch (InterruptedException e) {
23 // TODO Auto-generated catch block
24 e.printStackTrace();
25 }
26 }
27
28 }, "t1");
29 Thread t2 = new Thread(new Runnable() {
30
31 @Override
32 public void run() {
33 // TODO Auto-generated method stub
34 try {
35 staticMethodSynchronized.method2();
36 } catch (InterruptedException e) {
37 // TODO Auto-generated catch block
38 e.printStackTrace();
39 }
40 }
41
42 }, "t2");
43 t1.start();
44 t2.start();
45 }
46 }
在上述代碼中創建了兩個線程並命名為 t1,t2。 t1,t2 線程調用了 staticMethodSynchronized 類的兩個靜態同步方法 method1 和 method2。執行結果如下:

在 t1 線程開始執行後,即使 t1 線程睡眠了5s,線程 t2 中的 method2 方法仍然沒有得到執行。這是因為 t1 線程先執行的 method1 方法,持有了staticMethodSynchronized 類對象的鎖,且 method1 方法並沒有執行完,staticMethodSynchronized 類對象的鎖沒有被釋放,所以 comeInIn 方法無法對staticMethodSynchronized 類對象加鎖,就無法繼續執行,只能等到 t1 線程中的 method1 方法執行完畢,釋放 staticMethodSynchronized 類對象的鎖,method2 方法才能繼續執行。
三、實例方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
1 class ImmutableValue {
2 public synchronized void comeIn() throws InterruptedException{
3 System.out.println(Thread.currentThread().getName() + ": start");
4 Thread.sleep(5000);
5 System.out.println(Thread.currentThread().getName() + ": finish");
6 }
7 public void comeInIn() throws InterruptedException {
8 System.out.println(Thread.currentThread().getName() + ": start");
9 synchronized(this) {
10
11 }
12 System.out.println(Thread.currentThread().getName() + ": finish");
13 }
14 }
15 public class TestImmutableValue {
16 public static void main(String[] args) {
17 ImmutableValue im = new ImmutableValue();
18 Thread t1 = new Thread(new Runnable() {
19
20 @Override
21 public void run() {
22 // TODO Auto-generated method stub
23 try {
24 im.comeIn();
25 } catch (InterruptedException e) {
26 // TODO Auto-generated catch block
27 e.printStackTrace();
28 }
29 }
30
31 }, "t1");
32 Thread t2 = new Thread(new Runnable() {
33
34 @Override
35 public void run() {
36 // TODO Auto-generated method stub
37 try {
38 im.comeInIn();
39 } catch (InterruptedException e) {
40 // TODO Auto-generated catch block
41 e.printStackTrace();
42 }
43 }
44
45 }, "t2");
46 t1.start();
47 t2.start();
48 }
49 }
由以上代碼可以看到: 在 comeInIn 方法中,運用 synchronized(this) 制造同步塊,要執行同步塊內的代碼,就必須獲得 this 對象的鎖(調用 comeInIn 方法的對象)。
執行結果可能為:

由此執行結果可見:t1 線程先執行了 comeIn 方法,獲得了對象 im 的鎖,之後由於 t1 線程進入睡眠狀態,t2 線程得到運行,開始執行 comeInIn 方法,當執行到同步代碼塊時發現對象 im 已被加鎖,無法繼續執行。t1 線程睡眠結束之後繼續執行,結束後釋放對象 im 的鎖,t2 線程才能繼續執行。
四、靜態方法中使用 synchronized 關鍵字制造同步塊
同步塊中默認被加鎖的對象是此同步塊括號聲明中包含的對象。
1 class staticMethodSynchronized {
2 private static final Object OBJ = new Object();
3 public static void method1() throws InterruptedException {
4 System.out.println(Thread.currentThread().getName() + ": start");
5 synchronized(OBJ) {
6 System.out.println(Thread.currentThread().getName() + ": 獲得鎖");
7 System.out.println(Thread.currentThread().getName() + ": 釋放鎖");
8 }
9 System.out.println(Thread.currentThread().getName() + ": finish");
10 }
11 public static void method2() throws InterruptedException {
12 System.out.println(Thread.currentThread().getName() + ": start");
13 synchronized(OBJ) {
14 System.out.println(Thread.currentThread().getName() + ": 獲得鎖");
15 System.out.println(Thread.currentThread().getName() + ": 釋放鎖");
16 }
17 System.out.println(Thread.currentThread().getName() + ": finish");
18 }
19 }
20 public class TestStaticClassSynchronized {
21 public static void main(String[] args) {
22 Thread t1 = new Thread(new Runnable() {
23
24 @Override
25 public void run() {
26 // TODO Auto-generated method stub
27 try {
28 staticMethodSynchronized.method1();
29 } catch (InterruptedException e) {
30 // TODO Auto-generated catch block
31 e.printStackTrace();
32 }
33 }
34
35 }, "t1");
36 Thread t2 = new Thread(new Runnable() {
37
38 @Override
39 public void run() {
40 // TODO Auto-generated method stub
41 try {
42 staticMethodSynchronized.method2();
43 } catch (InterruptedException e) {
44 // TODO Auto-generated catch block
45 e.printStackTrace();
46 }
47 }
48
49 }, "t2");
50 t1.start();
51 t2.start();
52 }
53 }
在上述代碼中,兩個靜態方法中的同步塊都要獲得對象 OBJ 的鎖才能繼續向下執行,執行結果可能如下:

若 t1 線程先獲得鎖,則必須等到 t1 釋放鎖之後,t2 線程中同步代碼塊及其之後的代碼才能繼續執行,t2 線程先獲得鎖,t1 線程同理。
總之,我認為我們只需抓住一點:Java 中每個實例對象對應一把鎖且每個實例對象只有一把鎖,synchronized 關鍵字是通過對相應的實例對象加鎖來實現同步功能的(靜態方法為對相應的 class 對象加鎖)。在執行 synchronized 方法或 synchronized 同步塊之前,我們只需判斷其需要獲得的對象的鎖是否可獲得,就可判斷此方法或同步塊是否可得到執行。