java併發編程之synchronized關鍵字(五)

原博文地址:http://www.cnblogs.com/dolphin0520/p/3923737.html

1.什麼是線程安全問題:

1)多個線程同時訪問一個資源時,會導致程序的運行結果與預測的並不一致。這個資源在這裏成爲臨界資源or共享資源【一個對象,對象中的屬性,一個文件,一個數據庫等】。

eg:用戶註冊的時候,不能往數據庫裏面插入相同的名稱M,如果Thread-1和Thread-2同時讀到Yes【即可以插入】,那麼這個線程就會將數據插入到數據庫。

這裏就產生了一個隱患了,假若Thread-1和Thread-2的數據M不一樣就沒問題。要是剛好一樣,這樣數據庫數據就出現問題了。這時候就產生了線程安全問題。

2)當多個線程執行一個方法時,方法內部的局部變量並不是臨界資源,因爲方法是在棧上執行的,而Java棧是線程私有的,因此不會產生線程安全問題。

2.解決線程安全問題:

1)通常來說,都是在訪問臨界資源的代碼前面加上一個鎖,當訪問完臨界資源後釋放鎖,讓其他線程繼續訪問。即在同一個時刻只有一個線程能夠訪問臨界資源,術語上就是:同步互斥訪問。

2)java中使用synchronized【同步】和Lock【鎖】來實現同步互斥訪問。

3.synchronized關鍵字:

1)在Java中,每一個對象都擁有一個標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問

2)在java中,可以用synchronized來標記一個方法或者代碼塊,

3)當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便獲得了該對象的,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程纔會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。【synchronized作用原理】

4.synchronized方法塊:

1)加入synchronized前:

 

public class Test {
	 
    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();
        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();
    }  
}
 
class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入數據"+i);
            arrayList.add(i);
        }
    }
}

結果:

 

結論:兩個線程在同時執行insert方法。

2)加入synchronized關鍵字後:

 

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public synchronized void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入數據"+i);
            arrayList.add(i);
        }
    }
}

結果:

 

結論:

1)Thread-1插入數據是等Thread-0插入完數據之後才進行的。說明Thread-0和Thread-1是順序執行insert方法的。

注意點:

1)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程不能訪問該對象的其他synchronized方法。這個原因很簡單,因爲一個對象只有一把鎖,當一個線程獲取了該對象的鎖之後,其他線程無法獲取該對象的鎖,所以無法訪問該對象的其他synchronized方法。

2)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程能訪問該對象的非synchronized方法。這個原因很簡單,訪問非synchronized方法不需要獲得該對象的鎖,假如一個方法沒用synchronized關鍵字修飾,說明它不會使用到臨界資源,那麼其他線程是可以訪問這個方法的

3)如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會產生線程安全問題,因爲他們訪問的是不同的對象,所以不存在互斥問題。

5.synchronized代碼塊:

1)公式如下:

 

synchronized(synObject) {
         
    }

2)當在某個線程中執行這段代碼塊,該線程會獲取對象synObject的鎖,從而使得其他線程無法同時訪問該代碼塊。  

 

3)synObject可以是this,代表獲取當前對象的鎖,也可以是類中的一個屬性,代表獲取該屬性的鎖。

4)synchronized代碼塊可以進行如下改造:

 

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
     
    public void insert(Thread thread){
        synchronized (this) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入數據"+i);
                arrayList.add(i);
            }
        }
    }
}

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Object object = new Object();
     
    public void insert(Thread thread){
        synchronized (object) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入數據"+i);
                arrayList.add(i);
            }
        }
    }
}


好處:

 

1)synchronized代碼塊使用起來比synchronized方法要靈活得多。因爲也許一個方法中只有一部分代碼只需要同步,如果此時對整個方法用synchronized進行同步,會影響程序執行效率。而使用synchronized代碼塊就可以避免這個問題,synchronized代碼塊可以實現只對需要同步的地方進行同步。

2)每個類也會有一個鎖,它可以用來控制對static數據成員的併發訪問。

3)如果一個線程執行一個對象的非static synchronized方法,另外一個線程需要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,因爲訪問static synchronized方法佔用的是類鎖,而訪問非static synchronized方法佔用的是對象鎖,所以不存在互斥現象。

例子如下:

 

public class Test {
 
    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread(){
            @Override
            public void run() {
                insertData.insert();
            }
        }.start(); 
        new Thread(){
            @Override
            public void run() {
                insertData.insert1();
            }
        }.start();
    }  
}
 
class InsertData { 
    public synchronized void insert(){
        System.out.println("執行insert");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執行insert完畢");
    }
     
    public synchronized static void insert1() {
        System.out.println("執行insert1");
        System.out.println("執行insert1完畢");
    }
}

結果:

 

結論:

1)第一個線程裏面執行的是insert方法,不會導致第二個線程執行insert1方法發生阻塞現象。

注意點:

1)對於synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動釋放當前線程佔用的鎖,因此不會由於異常導致出現死鎖現象。

發佈了39 篇原創文章 · 獲贊 14 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章