這篇文章算是重構了,知識這種東西,理解的不深,用的就猶豫
爲什麼會線程不安全?
計算機在執行程序時,每條指令都是在CPU中執行的,而執行指令過程中會涉及到數據的讀取和寫入。程序運行過程中的臨時數據是存放在主存(物理內存)當中的,由於CPU執行速度很快,而從內存讀取數據和向內存寫入數據的過程跟CPU執行指令的速度比起來要慢的多,因此如果任何時候對數據的操作都要通過和內存的交互來進行,會大大降低指令執行的速度。
爲了處理這個問題,在CPU裏面就有了高速緩存(Cache)的概念。當程序在運行過程中,會將運算需要的數據從主內存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。這個高速緩存就是工作內存
規則:
Java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。
volatile如何解決這個問題?
對於多線程來說,注意三個特性:1.原子性,2.有序性,3.可見性,這三個特性保證了多線程的安全,這裏不講述
volatile保證了1.可見性;2.有序性; 注意不保證原子性;
可見性
含義:在多線程環境下,某個共享變量如果被其中一個線程給修改了,其他線程能夠立即知道這個共享變量已經被修改了,當其他線程要讀取這個變量的時候,最終會去主內存中讀取,即讀取的是最新的值
如int a = b+1,一個線程執行此操作,另一個線程執行讀,那麼只要a是+1後,讀取的時候一定是最新的值
如配置中心修改某值,若用volatile修飾此共享變量則會立即生效
有序性
當我們把代碼寫好之後,虛擬機不一定會按照我們寫的代碼的順序來執行
虛擬機在進行代碼編譯優化的時候,對於那些改變順序之後不會對最終變量的值造成影響的代碼,是有可能將他們進行重排序的
重排序可能會造成線程安全的問題
如果共享變量被volatile修飾,虛擬機會保證這個變量之前的代碼一定會比它先執行,而之後的代碼一定會比它慢執行。不論之前還是之後,它都無法保證重排序,只能保證當前修飾的變量前後是禁止重排序的
雖然volatile能夠保證其他線程立即可見,可見不代表 現有的值一定要重新獲取,不代表它是線程安全的,因爲多個線程修改共享變量就會有問題
volatile線程不安全的栗子
栗子:
int a = a+1
執行過程爲:
- 從內存中讀取a的值。
- 進行a = a + 1這個運算
- 把a的值寫回到內存中
如果是兩個線程同時讀取到b爲0,分別+1,寫回主內存的值就是1,線程不安全。
栗子:起100個線程,循環100次
public static volatile int t = 0;
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<100;i++){
for (int j=0;j<100;j++){
new Thread(new Runnable() {
@Override
public void run() {
t = t + 1;
}
}).start();
}
//打印t的值
System.out.println(t);
Thread.sleep(1000);
t = 0;
}
}
98
98
99
98
99
99
99
下面用AtomicInteger來保證線程安全
public static AtomicInteger t = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<100;i++){
for (int j=0;j<100;j++){
new Thread(new Runnable() {
@Override
public void run() {
int x = t.incrementAndGet();
System.out.println("t after "+x);
}
}).start();
}
//打印t的值
Thread.sleep(2000);//每隔2s置0重計數
System.out.println(t);
t = new AtomicInteger(0);
}
}
執行結果大家想想看應該是什麼樣的,然後自己運行一下
所以volatile在滿足以下兩個條件可以保證變量的線程安全問題
1.運算結果不依賴於變量的當前值,或者能夠確保只有單一的線程修改變量的值
2.變量不需要與其他狀態變量共同參與不變約束
所以可以在操作動作上 可以用JUC包,synchronized,Lock來控制原子性,保證線程安全
參考:https://www.cnblogs.com/kubidemanong/p/9505944.html