在JAVA中通過synchronized語句可以實現多線程并發。使用同步代碼塊,JVM保證同一時間只有一個線程可以擁有某一對象的鎖。鎖機制實現了多個線程安全地對臨界資源進行訪問。
同步代碼寫法如下:
代碼1:
1
2
3
4
5
|
Object obj = new Object(); ... synchronized (obj) { //TODO: 訪問臨界資源 } |
JAVA的多線程總是充滿陷阱,如果我們用Boolean作為被同步的對象,可能會出現以下兩種情況:
一. 以為對一個對象加鎖,實際同步的是不同對象。
代碼2:
1
2
3
4
5
6
7
8
9
10
11
|
private volatile Boolean isTrue = false ; publich void aMethod() { ... synchronized (isTrue) { isTrue = !isTrue; //TODO: 訪問臨界資源 isTrue = !isTrue; } ... } |
咋一看上面的代碼沒有問題,由于使用了synchronized(isTrue)同一時間只能有一個線程訪問臨界資源,但事實并不是這樣。因為false和true這兩個常量對應著兩個不同的對象。當isTrue產生變化時,很可能導致不同的線程同步了不同的對象。JAVA的自動裝箱會將false變為Boolean.FALSE,將true變為Boolean.TRUE(同時這也說明了此處若將false改為Boolean.FALSE其結果也是一樣的)。寫一個以上情況的測試代碼如下:
代碼3:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class BooleanTest { private volatile Boolean isTrue = Boolean.FALSE; //此處用false也一樣 public void aMethod() { for ( int i= 0 ;i< 10 ;i++) { Thread t = new Thread() { public void run() { synchronized (isTrue) { isTrue = !isTrue; System.out.println(Thread.currentThread().getName() + " - isTrue=" + isTrue); try { Double ran = 1000 * Math.random(); Thread.sleep(ran.intValue()); } catch (InterruptedException e) {} if (!isTrue) System.out.println(Thread.currentThread().getName() + " - Oh, No!" ); isTrue = !isTrue; } } }; t.start(); } } public static void main(String... args) { BooleanTest bt = new BooleanTest(); bt.aMethod(); } } |
運行以上代碼,不時的會看到 " - Oh, No!",表示不同的線程同時進入到synchronized代碼塊中。
二. 以為同步的是不同對象,實際是一個對象。
有時候我們可能希望在多個對象上進行同步,如果使用了Boolean作為被同步對象,很可能會導致本來應該沒有關系的兩個同步塊使用了相同對象的鎖。示例如下:
代碼4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private volatile Boolean aBoolean = Boolean.FALSE; private volatile Boolean anotherBoolean = false ; public void aMethod() { ... synchronized (aBoolean) { //TODO: 訪問臨界資源1 } ... } public void anotherMethod() { ... synchronized (anotherBoolean) { //TODO: 訪問臨界資源2 } ... } |
假設原本aMethod和anotherMethod分別會被兩組沒有關系的線程調用。但是由于Boolean.FALSE和false指向的是同一個對象,可能導致對臨界資源2的訪問被臨界資源1阻塞了(反之亦然)。
以上兩種情況說明,在使用同步塊時,盡量不用使用Boolean對象作為被同步對象,不然可能會出現意想不到的問題,或者對以后的代碼修改造成陷阱。
從此也可以看出,任何對常量的同步都是有風險的。如果一定要對 Boolean 進行同步,一定要用 new 操作符來創建 Boolean 對象。