變量分爲哪幾類安全
全局變量 = 屬性(靜態的、非靜態的) 局部變量 = 本地變量、參數bash
多線程間共享數據多線程
全局變量:靜態變量或共享對象併發
併發線程能不能看到共享變量的最新值,這就是併發中變量可見性問題jvm
(1) 爲何不可見?性能
(2) 怎樣才能可見優化
使用synchroized關鍵字,對線程主體進行包裝spa
使用volatile關鍵字修飾共享變量線程
JAVA內存模型及操做規範code
(1) 共享變量必須存放在主內存。
(2) 線程有本身的工做內存,線程只可操做本身的工做內存。
(3) 線程要操做共享變量,需從主內存中讀取變量值到工做內存,改變後再從工做內存同步到主內存。
(1) 數據存在多份拷貝,致使多線程同時讀寫同一變量時,存在數據不許確的問題。即線程安全問題。
(2) 因此要使用線程同步或鎖處理。
變量在線程1中更改,在線程2中能看到該變量的最新值
(1) 線程1修改A後,必須立刻同步回主內存
(2) 線程2使用A前,須要從新從主內存讀取到工做內存
JAVA內存模型-同步交互協議,規定了8種原子操做
(1) lock(鎖定) 將主內存中的變量鎖定,爲一個線程所獨佔。
(2) unlock(解鎖) 將lock加的鎖解除,容許其它線程訪問主內存中的該變量。
(3) read(讀取) 做用於主內存變量,將主內存中的變量值讀取到工做內存(從主內存讀取到寄存器)。
(4) load(載入) 做用於工做內存變量,將read讀取到的值保存到工做內存中的變量副本中(寄存器變量載入到工做內存)。
(5) use(使用) 做用於工做內存變量,將值傳遞給線程的代碼執行引擎。
(6) assign(賦值) 做用於工做內存變量,將執行引擎處理返回的值從新賦值給變量副本。
(7) store(存儲) 做用於工做內存變量,將變量副本的值傳送到主內存中(從工做內存讀取到寄存器)。
(8) write(寫入) 做用於主內存變量,將store傳入的值寫入到主內存的共享變量中(寄存器變量寫入到主內存)。
注意:任意間的組合操做,不是原子的。
(1) 將一根變量從主內存複製到工做內存,要順序執行read、load操做;要將變量從工做內存同步回主內存要順序執行stroe、write操做。只要求順序執行,不必定是連續執行(即原子操做)。
(2) 作了assign操做,必須同步回主內存。不能沒作assign操做,同步回主內存。(不必定assign後,立刻同步回主內存)
併發中保證變量可見性方法 final synchronized volatile
synchronized語義規範(既能夠保證可見性,又能夠保證線程安全)
(1) 進入同步塊前,先清空工做內存中的共享變量,從主內存中從新加載。
(2) 解鎖前,必須把修改的共享變量同步回主內存。
(3) 最核心的,是synchronized有鎖機制,只有得到鎖的線程才能操做共享資源。(悲觀鎖)
volatile語義規範(保證可見性,保證工做內存的變量和主內存的變量的值一致)
(1) 使用volatile變量時,必須從新從主內存加載,而且read、load是連續的。
(2) 修改volatile變量後,必須立刻同步回主內存,而且store、write是連續的。
volatile能作到線程安全嗎?(不能保證原子性)
(1) 不能,由於它沒有鎖機制,線程可併發操做共享資源。
(1) 線程A和線程B的部分代碼
線程A
content = initContent();
isInit = true;
複製代碼
線程B
while(isinit) {
content.oper();
}
複製代碼
(2) jvm優化指令重排序後
線程A(由於兩行代碼,沒有任何關係)
isInit = true;
content = initContent();
複製代碼
(3) 當兩個線程併發執行時,就可能出現線程B中content發生空異常。
volatile的使用範圍
(1) 只可修飾成員變量(靜態、非靜態)。由於用來保證共享變量可見性,共享變量只能是全局變量。
(2) 多線程併發下,才須要使用。
單例模式中對volatile關鍵字的使用