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關鍵字?(整合版)