1. 程式人生 > 其它 >Java中的執行緒安全和非執行緒安全

Java中的執行緒安全和非執行緒安全

執行緒安全:就是當多執行緒訪問時,採用了加鎖的機制;即當一個執行緒訪問該類的某個資料時,會對這個資料進行保護,其他執行緒不能對其訪問,直到該執行緒讀取完之後,其他執行緒才可以使用。防止出現數據不一致或者資料被汙染的情況。
執行緒不安全:就是不提供資料訪問時的資料保護,多個執行緒能夠同時操作某個資料,從而出現數據不一致或者資料汙染的情況。
簡單而言,非執行緒安全是指多執行緒操作同一個物件可能會出現問題。而執行緒安全則是多執行緒操作同一個物件不會有問題。

存線上程安全問題必須滿足三個條件:
1.有共享變數
2.處在多執行緒環境下
3.共享變數有修改操作。

執行緒安全工作原理:
jvm中有一個Main Memory,每一個執行緒也有自己的Working Memory,一個執行緒對於一個變數variable進行操作的時候,都需要在自己的working memory裡建立一個變數的副本copy,操作完之後再寫入main memory。
當多個執行緒操作同一個變數variable,就可能出現不可預知的結果。

-------------------------------------------------------------------------------------------------------------

note:

1.主記憶體(Main Memory)
主記憶體可以簡單理解為計算機當中的記憶體,但又不完全等同。主記憶體被所有的執行緒所共享,對於一個共享變數(比如靜態變數,或是堆記憶體中的例項)來說,主記憶體當中儲存了它的“本尊”。
2.工作記憶體(Working Memory)
工作記憶體可以簡單理解為計算機當中的CPU快取記憶體,但又不完全等同。每一個執行緒擁有自己的工作記憶體,對於一個共享變數來說,工作記憶體當中儲存了它的“副本”。

執行緒對共享變數的所有操作都必須在工作記憶體進行,不能直接讀寫主記憶體中的變數。不同執行緒之間也無法訪問彼此的工作記憶體,變數值的傳遞只能通過主記憶體來進行。
(因為直接操作主記憶體太慢,所以jvm才利於效能比較高的工作記憶體,可以類比CPU、快取記憶體和記憶體)

Java記憶體模型(JMM,Java Memory Model)如圖:

-------------------------------------------------------------------------------------------------------------

例:開啟10個執行緒,每個執行緒當中讓靜態變數count自增100次。執行之後會發現,最終count的結果值未必是1000,有可能小於1000。(這裡count是執行緒間的共享變數)

public class Test {
    public static int count = 0;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //開啟10個執行緒
        for(int i = 0; i < 10; i++){
            new Thread(
                    new Runnable(){
                        public void run(){
                            try{
                                Thread.sleep(1);
                            }
                            catch(InterruptedException e){
                                e.printStackTrace();
                            }
                            //每個執行緒中讓count自增100次
                            for(int j = 0; j < 100; j++){
                                count++;
                            }
                        }
                    }).start();
        }
        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("count= "+count);
    }
}
//執行結果:count= 952

假設count=2時,當執行緒1在將count++的結果(count=3)寫入記憶體之前,執行緒2已經從記憶體中讀取了count的值,並在這個值(count=2)上進行++操作,先於執行緒1將count=3寫入了記憶體,這是執行緒1再將count=3寫入記憶體,就存在錯誤了。

使用synchronized同步方法改進:

public class VolatileTest {
    public static int count = 0;

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 開啟10個執行緒
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 每個執行緒中讓count自增100次
                    for (int j = 0; j < 100; j++) {
                        count(); //count++;
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count= " + count);
    }

    public static synchronized void count() {
        count++;
    }
}

執行結果:

Java中實現執行緒安全的方法:
1. 最簡單的方式,使用Synchronization關鍵字
用synchronized的關鍵是建立一個監控(monitor),這個monitor可以是要修改的變數,也可以是其他自己認為合適的物件(方法),然後通過給這個monitor加鎖來實現執行緒安全,每個執行緒在獲得這個鎖之後,要執行完載入(load)到working memory再到使用(use) && 指派(assign)到 儲存(store)再到main memory的過程。才會釋放它得到的鎖。這樣就實現了所謂的執行緒安全。
2. 使用java.util.concurrent.atomic 包中的原子類,例如 AtomicInteger
3. 使用java.util.concurrent.locks 包中的鎖
4. 使用執行緒安全的集合ConcurrentHashMap
5. 使用volatile關鍵字,保證變數可見性(直接從記憶體讀,而不是從執行緒cache讀)
參考:

https://blog.csdn.net/u011389474/article/details/54602812
https://www.zhihu.com/question/49855966
java 執行緒安全 synchronized
漫畫:什麼是volatile關鍵字?(整合版)