JAVA synchronized關鍵字解析原理

synchronized關鍵字可以作爲函數的修飾符,也可作爲函數內的語句,也就是平時說的同步方法和同步語句塊。如果再細的分類,synchronized可作用於instance變量、object reference(對象引用)、static函數和class literals(類名稱字面常量)身上。

在進一步闡述之前,我們需要明確幾點:

A.無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖――而且同步方法很可能還會被其他線程的對象訪問。

B.每個對象只有一個鎖(lock)與之相關聯。

C.實現同步是要很大的系統開銷作爲代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。

接着來討論synchronized用到不同地方對代碼產生的影響:

假設P1、P2是同一個類的不同對象,這個類中定義了以下幾種情況的同步塊或同步方法,P1、P2就都可以調用它們。

1. 把synchronized當作函數修飾符時,示例代碼如下:

Public synchronized void methodAAA()

{

//….

}

這也就是同步方法,那這時synchronized鎖定的是哪個對象呢?它鎖定的是調用這個同步方法對象。也就是說,當一個對象P1在不同的線程中執行這個同步方法時,它們之間會形成互斥,達到同步的效果。但是這個對象所屬的Class所產生的另一對象P2卻可以任意調用這個被加了synchronized關鍵字的方法。

上邊的示例代碼等同於如下代碼:

public void methodAAA()

{

synchronized (this)      // (1)

{

      //…..

}

}

(1)處的this指的是什麼呢?它指的就是調用這個方法的對象,如P1。可見同步方法實質是將synchronized作用於object reference。――那個拿到了P1對象鎖的線程,纔可以調用P1的同步方法,而對P2而言,P1這個鎖與它毫不相干,程序也可能在這種情形下襬脫同步機制的控制,造成數據混亂:(

2.同步塊,示例代碼如下:

          public void method3(SomeObject so)

              {

                    synchronized(so)

{

      //…..

}

}

這時,鎖就是so這個對象,誰拿到這個鎖誰就可以運行它所控制的那段代碼。當有一個明確的對象作爲鎖時,就可以這樣寫程序,但當沒有明確的對象作爲鎖,只是想讓一段代碼同步時,可以創建一個特殊的instance變量(它得是一個對象)來充當鎖:

class Foo implements Runnable

{

      private byte[] lock = new byte[0]; // 特殊的instance變量

    Public void methodA()

{

      synchronized(lock) { //… }

}

//…..

}

注:零長度的byte數組對象創建起來將比任何對象都經濟――查看編譯後的字節碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

3.將synchronized作用於static 函數,示例代碼如下:

    Class Foo

{

public synchronized static void methodAAA()  // 同步的static 函數

{

//….

}

public void methodBBB()

{

      synchronized(Foo.class)  // class literal(類名稱字面常量)

}

      }

  代碼中的methodBBB()方法是把class literal作爲鎖的情況,它和同步的static函數產生的效果是一樣的,取得的鎖很特別,是當前調用這個方法的對象所屬的類(Class,而不再是由這個Class產生的某個具體對象了)。

記得在《Effective Java》一書中看到過將 Foo.class和 P1.getClass()用於作同步鎖還不一樣,不能用P1.getClass()來達到鎖這個Class的目的。P1指的是由Foo類產生的對象。

可以推斷:如果一個類中定義了一個synchronized的static函數A,也定義了一個synchronized 的instance函數B,那麼這個類的同一對象Obj在多線程中分別訪問A和B兩個方法時,不會構成同步,因爲它們的鎖都不一樣。A方法的鎖是Obj這個對象,而B的鎖是Obj所屬的那個Class。

小結如下:

搞清楚synchronized鎖定的是哪個對象,就能幫助我們設計更安全的多線程程序。

還有一些技巧可以讓我們對共享資源的同步訪問更加安全:

1.  定義private 的instance變量+它的 get方法,而不要定義public/protected的instance變量。如果將變量定義爲public,對象在外界可以繞過同步方法的控制而直接取得它,並改動它。這也是JavaBean的標準實現方式之一。

2.  如果instance變量是一個對象,如數組或ArrayList什麼的,那上述方法仍然不安全,因爲當外界對象通過get方法拿到這個instance對象的引用後,又將其指向另一個對象,那麼這個private變量也就變了,豈不是很危險。這個時候就需要將get方法也加上synchronized同步,並且,只返回這個private對象的clone()――這樣,調用端得到的就是對象副本的引用了。


******************

synchronized的作用(一)

synchronized的作用

一、同步方法

public synchronized void methodAAA(){

//….

}

鎖定的是調用這個同步方法的對象

測試:

a、不使用這個關鍵字修飾方法,兩個線程調用同一個對象的這個方法。

目標類:


1public class TestThread {

2    public  void execute(){  //synchronized,未修飾

3        for(int i=0;i<100;i++){

4              System.out.println(i);

5          }   

6      }

7}


線程類:


1public class ThreadA implements Runnable{

2      TestThread test=null;

3    public ThreadA(TestThread pTest){  //對象有外部引入,這樣保證是同一個對象

4          test=pTest;

5      }

6    public void run() {

7          test.execute();

8      }

9}

調用:

1TestThread test=new TestThread();

2Runnable runabble=new ThreadA(test);

3Thread a=new Thread(runabble,"A");               

4a.start();

5Thread b=new Thread(runabble,"B");

6b.start();


結果:

輸出的數字交錯在一起。說明不是同步的,兩個方法在不同的線程中是異步調用的。

b、修改目標類,增加synchronized修飾


1public class TestThread {

2    public synchronized  void execute(){  //synchronized修飾

3        for(int i=0;i<100;i++){

4              System.out.println(i);

5          }   

6      }

7}


結果:

輸出的數字是有序的,首先輸出A的數字,然後是B,說明是同步的,雖然是不同的線程,但兩個方法是同步調用的。

注意:上面雖然是兩個不同的線程,但是是同一個實例對象。下面使用不同的實例對象進行測試。

c、每個線程都有獨立的TestThread對象。

目標類:


1public class TestThread {

2    public synchronized void execute(){  //synchronized修飾

3        for(int i=0;i<100;i++){

4              System.out.println(i);

5          }   

6      }

7}

線程類:

1public class ThreadA implements Runnable{

2    public void run() {

3          TestThread test=new TestThread();

4          test.execute();

5      }

6}

7

調用:

1Runnable runabble=new ThreadA();

2Thread a=new Thread(runabble,"A");               

3a.start();

4Thread b=new Thread(runabble,"B");

5b.start();


結果:

輸出的數字交錯在一起。說明雖然增加了synchronized 關鍵字來修飾方法,但是不同的線程調用各自的對象實例,兩個方法仍然是異步的。

***********************

synchronized的作用(二)

引申:

對於這種多個實例,要想實現同步即輸出的數字是有序並且按線程先後順序輸出,我們可以增加一個靜態變量,對它進行加鎖(後面將說明鎖定的對象)。

修改目標類:


1public class TestThread {

2    private static Object lock=new Object(); //必須是靜態的。

3    public  void execute(){

4        synchronized(lock){

5            for(int i=0;i<100;i++){

6                  System.out.println(i);

7              }   

8          }

9      }

10}


二、同步代碼塊


1public void method(SomeObject so){

2    synchronized(so)

3      //…..

4      }

5}


鎖定一個對象,其實鎖定的是該對象的引用(object reference)

誰拿到這個鎖誰就可以運行它所控制的那段代碼。當有一個明確的對象作爲鎖時,就可以按上面的代碼寫程序,但當沒有明確的對象作爲鎖,只是想讓一段代碼同步時,可以創建一個特殊的instance變量(它必須是一個對象)來充當鎖(上面的解決方法就是增加了一個狀態鎖)。

a、鎖定一個對象,它不是靜態的

private byte[] lock = new byte[0]; // 特殊的instance變量

目標類:


1public class TestThread {

2    private Object lock=new Object();

3    public  void execute(){

4        synchronized(lock){  //增加了個鎖,鎖定了對象lock,在同一個類實例中,是線程安全的,但不同的實例還是不安全的。

5

6因爲不同的實例有不同對象鎖lock

7            for(int i=0;i<100;i++){

8                  System.out.println(i);

9              }   

10          }

11      }

12} 


其實上面鎖定一個方法,等同於下面的:


1public void execute(){ 

2    synchronized(this){  //同步的是當然對象

3        for(int i=0;i<100;i++){

4              System.out.println(i);

5          }   

6      }

7}


***************************

synchronized的作用(三)

b、鎖定一個對象或方法,它是靜態的

這樣鎖定,它鎖定的是對象所屬的類

public synchronized    static void execute(){

      //...

}

等同於


1public class TestThread {

2    public static void execute(){

3        synchronized(TestThread.class){

4            //

5          }

6      }

7}


測試:

目標類:


1public class TestThread {

2    private static Object lock=new Object();

3    public synchronized static void execute(){  //同步靜態方法

4        for(int i=0;i<100;i++){

5              System.out.println(i);

6          }   

7      }

8    public static void execute1(){

9        for(int i=0;i<100;i++){

10              System.out.println(i);

11          }   

12      }

13    public void test(){

14          execute();    //輸出是有序的,說明是同步的

15        //execute1();  //輸出是無須的,說明是異步的

16      }

17}


線程類:調用不同的方法,於是建立了兩個線程類


1public class ThreadA implements Runnable{

2    public void run() {

3          TestThread.execute();//調用同步靜態方法

4      }

5}

6public class ThreadB implements Runnable{

7    public void run() {

8          TestThread test=new TestThread();

9          test.test();//調用非同步非靜態方法

10      }

11}


調用:


1Runnable runabbleA=new ThreadA();

2Thread a=new Thread(runabbleA,"A");               

3a.start();

4Runnable runabbleB=new ThreadB();

5Thread b=new Thread(runabbleB,"B");               

6b.start();


synchronized的作用(四)

注意:

1、用synchronized 來鎖定一個對象的時候,如果這個對象在鎖定代碼段中被修改了,則這個鎖也就消失了。看下面的實例:

目標類:


1public class TestThread {

2    private static final class TestThreadHolder {

3            private static TestThread theSingleton = new TestThread();

4            public static TestThread getSingleton() {

5                return theSingleton;

6              }

7            private TestThreadHolder() {

8              }

9          }

10   

11    private Vector ve =null;

12    private Object lock=new Object();

13    private TestThread(){

14          ve=new Vector();

15          initialize();

16      }

17    public static TestThread getInstance(){

18        return TestThreadHolder.getSingleton();

19      }

20    private void initialize(){

21        for(int i=0;i<100;i++){

22              ve.add(String.valueOf(i));

23          }

24      }

25    public void reload(){

26        synchronized(lock){

27              ve=null;           

28              ve=new Vector();

29                        //lock="abc";

30            for(int i=0;i<100;i++){

31                  ve.add(String.valueOf(i));

32              }

33          }

34          System.out.println("reload end");

35      }

36   

37    public boolean checkValid(String str){

38        synchronized(lock){

39              System.out.println(ve.size());

40            return ve.contains(str);

41          }

42      }

43}


說明:在reload和checkValid方法中都增加了synchronized關鍵字,對lock對象進行加鎖。在不同線程中對同一個對象實例分別調用reload和checkValid方法。

在reload方法中,不修改lock對象即註釋lock="abc"; ,結果在控制檯輸出reload end後才輸出100。說明是同步調用的。

如果在reload方法中修改lock對象即去掉註釋,結果首先輸出了一個數字(當前ve的大小),然後輸出reload end。說明是異步調用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章