如何解決線程安全問題

如何解決線程安全問題

怎麼解決線程的安全問題呢?java

基本上全部解決線程安全問題的方式都是採用「序列化臨界資源訪問」的方式,即在同一時刻只有一個線程操做臨界資源,操做完了才能讓其餘線程進行操做,也稱做同步互斥訪問。安全

在Java中通常採用synchronizedLock來實現同步互斥訪問。多線程

synchronized關鍵字

首先咱們先來了解一下互斥鎖,互斥鎖:就是能達到互斥訪問目的的鎖。ide

若是對一個變量加上互斥鎖,那麼在同一時刻,該變量只能有一個線程能訪問,即當一個線程訪問臨界資源時,其餘線程只能等待。優化

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

在咱們編寫代碼的時候,可使用synchronized修飾對象的方法或者代碼塊,當某個線程訪問這個對象synchronized方法或者代碼塊時,就獲取到了這個對象的鎖,這個時候其餘對象是不能訪問的,只能等待獲取到鎖的這個線程執行完該方法或者代碼塊以後,才能執行該對象的方法。spa

咱們來看個示例進一步理解synchronized關鍵字:線程

public class Example {
    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);
        }
    }
}

這段代碼的執行是隨機的(每次結果都不同):code

Thread-0在插入數據0` `Thread-1在插入數據0` `Thread-1在插入數據1` `Thread-1在插入數據2` `Thread-1在插入數據3` `Thread-1在插入數據4` `Thread-0在插入數據1` `Thread-0在插入數據2` `Thread-0在插入數據3` `Thread-0在插入數據4

如今咱們加上synchronized關鍵字來看看執行結果:對象

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

輸出:

Thread-0在插入數據0` `Thread-0在插入數據1` `Thread-0在插入數據2` `Thread-0在插入數據3` `Thread-0在插入數據4` `Thread-1在插入數據0` `Thread-1在插入數據1` `Thread-1在插入數據2` `Thread-1在插入數據3` `Thread-1在插入數據4

能夠發現,線程1會等待線程0插入完數據以後再執行,說明線程0和線程1是順序執行的。

從這兩個示例中,咱們能夠知道synchronized關鍵字能夠實現方法同步互斥訪問。

在使用synchronized關鍵字的時候有幾個問題須要咱們注意:

  1. 在線程調用synchronized的方法時,其餘synchronized的方法是不能被訪問的,道理很簡單,一個對象只有一把鎖;
  2. 當一個線程在訪問對象的synchronized方法時,其餘線程能夠訪問該對象的非synchronized方法,由於訪問非synchronized不須要獲取鎖,是能夠隨意訪問的;
  3. 若是一個線程A須要訪問對象object1synchronized方法fun1,另一個線程B須要訪問對象object2synchronized方法fun1,即便object1object2是同一類型),也不會產生線程安全問題,由於他們訪問的是不一樣的對象,因此不存在互斥問題。

synchronized代碼塊

synchronized代碼塊對於咱們優化多線程的代碼頗有幫助,首先咱們來看看它長啥樣:

synchronized(synObject) {}

當在某個線程中執行該段代碼時,該線程會獲取到該對象的synObject鎖,此時其餘線程沒法訪問這段代碼塊,synchronized的值能夠是this表明當前對象,也能夠是對象的屬性,用對象的屬性時,表示的是對象屬性的鎖。

有了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);
            }
        }
    }
}

上述代碼就是synchronized代碼塊添加鎖的兩種方式,能夠發現添加synchronized代碼塊,要比直接在方法上添加synchronized關鍵字更加靈活。

當咱們用sychronized關鍵字修飾方法時,這個方法只能同時讓一個線程訪問,可是有時候極可能只有一部分代碼須要同步,而這個時候使用sychronized關鍵字修飾的方法是作不到的,可是使用sychronized代碼塊就能夠實現這個功能。

而且若是一個線程執行一個對象的非static synchronized方法,另一個線程須要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,由於訪問static synchronized方法佔用的是類鎖,而訪問非static synchronized方法佔用的是對象鎖,因此不存在互斥現象。

來看一段代碼:

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);
            }
        }
    }
}

執行結果:

執行insert` `執行insert1` `執行insert1完畢` `執行insert完畢
相關文章
相關標籤/搜索