(一)併發中的變量可見性問題
1.變量分類?
全局變量
局部變量
屬性(靜態的、非靜態的) 全局變量
本地變量 局部變量
參數 局部變量
2、如何在多個線程間共享數據?
用全局變量:靜態變量,或共享對象
3.一個變量在線程1中被改變值了,在線程2中能看到該變量的最新值嗎?
import java.util.concurrent TimeUnit
public class VisbilityDemo{
//狀態標識
private static boolean is=true;
Public static void main(String[] args){
new Thread(new Runable(){
public void run(){
int i=0;
while(VisibilityDemo.is) {
i++;
}
System.out.println(i);
}
}),start();
try {
TimeUnit.SECONDS.slee(2);
}catch (InterruptedException e){
e.printStackTrace();
}
//設置is爲false,使上面的線程結束while循環
VisibilityDemo.is = false;
System.out.println("被設置爲false了");
}
}
線程會停止循環,打印出i的值嗎?不會
*併發的線程能不能看到變量的最新值,這就是併發中的變量可見性問題
怎樣才能可見?
方式一:同步關鍵字:synchronized
#####方式二:volatile
(二)變量可見性、線程安全問題原因
1.Java內存模型
Java內存模型以及操作規範變量
1.共享變量必須存放在主存中;
2.線程有自己的工作內存,線程只可操作自己的工作內存;
3.線程要操作共享變量,需從主存中讀取到工作內存,改變值後需從工作內存同步到主存中。
Java內存模型會帶來什麼問題?
*有變量A,多線程併發對其累加會有什麼問題?如三個線程併發操作變量A,大家讀取A時讀到A=0,
都對A+1,在將值同步會主內存,結果是多少?
結果是1.
這就是線程安全問題。
如何解決線程安全問題?
內存模型也產生了變量可見性問題。
如何讓線程2使用A時看到最新值?
1.線程1修改A後必須馬上同步回主內存
2.線程2使用A前需從主內存讀取到工作內存。
疑問一:使用前不會重新從主內存讀取到工作內存嗎?
疑問二:修改後不會立馬同步會主內存嗎?
2.Java內存模型–同步交互協議
規定了8種原子操作:
1.lock(鎖定):將主內存中的變量鎖定,爲一個線程所獨佔
2.unlock(解鎖):將lock加的鎖定解除,此時其它的線程可以有機會訪問此變量
3.read(讀取):作用於主存變量,將主存中的變量讀到工作內存當中
4.load(載入):作用於工作內存變量,將read讀取的值保存到工作內存中的變量副本中
5.use(使用):作用於工作內存變量,將值傳遞給線程的代碼執行引擎
6.assign(賦值):作用於工作內存變量,將執行引擎處理返回的值重新賦值給變量副本
7.store(存儲):作用於工作內存變量,將變量副本的值傳送到主內存中
8.write(寫入):作用於主內存變量,將store傳送過來的值寫入到主內存的共享變量中
操作規範:
1.將一個變量從主內存複製到工作內存要順序執行read load操作;要將變量從工作內同步
回主內存要順序執行store、write操作。只要求順序執行,不一定是連續執行
2.做了assign操作,必須同步回主存。不能沒做assign,同步回內存。
併發中保持變量可見性的方式:
1.final變量
//final不可變變量
private final int var1 = 1;
2.Synchronized
while (VisibilityDemo.is){
synchronized(this){
i++;
}
}
3.用volatile修飾
//狀態標識
private static volatitle boolean is =true;
1synchronized怎樣做到可見性?
1synchronized語義規範:
1.進入同步塊前,先清空工作內存中的共享變量,從主內存中重新加載。
2.解鎖前必須把修改的共享變量同步回主內存
1synchronized如何做到線程安全的?
1.鎖機制保護共享資源,只有獲得鎖的線程纔可操作共享資源;
2.synchronized語義規範保證了修改共享資源後,會同步回主存,就做到了線程安全。
(三)volatile關鍵字解密
1.volatile怎樣做到可見性
volatile語義規範:
1.使用volatile變量時,必須重新從主內存加載,並且read 、load是連續的。
2.修改volatile變量後,必須立馬同步回主內存,並且store、 write是連續的。
volatile能做到線程安全嗎?
1.不能,因爲它沒有鎖機制,線程可併發操作共享資源。
1爲什麼使用volatile?
synchronized可以保證可見性,爲什麼要用volatile?
1.主要原因:使用volatitle比synchronized簡單
2.volatile性能比synchronized稍好。
*如果是保證可見性,用volatile;保證線程安全用synchronized。
volatile還有什麼用途?
volatile可用於限制局部代碼指令重排序
1.線程A
content = initCoutent(); //(1)
islint = true;//(2)
線程B
is(islint){//(3)
content.operation();//(4)
}
2.jvm優化指令排序後,代碼的執行順序可能如下:
線程A
islint = true;//(1)
content = initCoutent(); //(2)
3.當兩個線程併發執行時,就可能出現線程B中拋空指針異常。
4.當我們在變量上加volatile修飾時,則用到該變量的塊中就不會進行指令重排優化。
volatile的使用場景
volatitle的使用範圍:
1.volatile只可修飾成員變量(靜態的、非靜態的)【全局變量】
2.多線程併發下,才需要使用它。
volatitle典型的應用場景:
1、只有一個修改者,多個使用者,要求保證可見性的場景
2、狀態標識,如示例中介紹的標識
2、數據定期發佈,都個獲取者
volatile保證併發的情況下,不需要太多的線程進入到鎖機制