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 關鍵字來修飾方法,但是不同的線程調用各自的對象實例,兩個方法仍然是異步的。
引申:
對於這種多個實例,要想實現同步即輸出的數字是有序並且按線程先後順序輸出,我們可以增加一個靜態變量,對它進行加鎖(後面將說明鎖定的對象)。
修改目標類:
 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}
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();
注意:
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。說明是異步調用的。
2、單例模式中對多線程的考慮
 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    private Vector ve =null;
11    private Object lock=new Object();
12    private TestThread(){
13        ve=new Vector();
14        initialize();
15    }

16    public static TestThread getInstance(){
17        return TestThreadHolder.getSingleton();
18    }

19        '''
20}
說明:增加了一個內部類,在內部類中申明一個靜態的對象,實例化該單例類,初始化的數據都在單例類的構造函數中進行。這樣保證了多個實例同時訪問的時候,初始化的數據都已經成功初始化了。
總結:
A. 無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象,而不是把一段代碼或函數當作鎖,所以首先應知道需要加鎖的對象
B.每個對象只有一個鎖(lock)與之相關聯。
C.實現同步是要很大的系統開銷作爲代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章