一、多執行緒的三大性質
原子性;可見性、有序性
二、原子性
原子性介紹
原子性是指:一個操作時不可能中斷的,要么全部執行成功要么全部執行失敗,有著同生共死的感覺,即使在多執行緒一起執行的時候,一個操作一旦開始,就不會被其他執行緒所干擾,
先看看哪些是原子操作,哪些不是原子操作:
int a=10; //1
a++; //2
int b=a; //3
a=a+1; //4
上面這四個陳述句中只有第1個陳述句是原子操作,將10賦值給執行緒作業記憶體的變數a,而陳述句2 a++,實際上包含了三個操作:讀取變數a的值;對進行加1的操作,將計算后的值在賦值給變數a,而這三個操作都無法構成原子操作,對陳述句3,4的分析同理,這兩條陳述句不具備原子性,當然,Java記憶體模型中定義了8中操作都是原子的,不可再分的,
lock(鎖定):作用于主記憶體中的變數,它把一個變數標示為一個執行緒獨占的狀態;
unlock(解鎖):作用于主記憶體中的變數,它把一個處于鎖定狀態的變數釋放出來,釋放后的變數才可以被其他執行緒鎖定;
read(讀取):作用于主記憶體的變數,它把一個變數的值從主記憶體傳輸到執行緒的作業記憶體,以便后面的load動作使用;
load(載入):作用于作業記憶體中的變數,它把read操作從主記憶體中得到的變數值放入作業記憶體中的變數副本;
use(使用):作用于作業記憶體中的變數,它把作業記憶體中一個變數的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變數的值得位元組碼指令時將會執行這個操作;
assign(賦值):作用于作業記憶體中的變數,它把一個執行引擎接受到的值賦給作業記憶體的變數,每當虛擬機遇到一個給變數賦值的位元組碼指令時執行這個操作;
store(存盤):作用于作業記憶體的變數,它把作業記憶體中一個變數的值傳遞給主記憶體中以便隨后的write操作使用;
write(操作):作用于主記憶體的變數,它把store操作從作業記憶體中得到的變數的值放入主記憶體的變數中,
上面的這些操作時相當底層的,那么如何理解這些指令呢?比如:把一個變數從主記憶體復制到作業記憶體中就需要執行read,load操作,將作業記憶體同步到主記憶體中就需要執行store,write操作,注意的是:Java記憶體模型只是要求上述兩個操作時順序執行的并不是連續執行的,也就是說read和load之間可以插入其他指令,store和write也可以插入其他指令,比如對主記憶體中的a,b進行訪問就可以出現這樣的操作順序:read a,read b,load b,load a
由原子性操作變數read,load,use,assign,store,write可以大致認為基本資料型別的訪問讀寫具備原子性(例外的就是long和double的非原子性協定)
三、synchronized和volatile的原子性
synchronized
上面一共有八條原子操作,其中六條可以滿足基本資料型別的訪問讀寫具備原子性,還剩下lock和unlock兩條原子操作,
如果我們需要大范圍的原子操作就可以使用lock和unlock原子操作,盡管JVM沒有吧lock和unlock開放給我們,但JVM以更高層次的指令monitorenter和monitorexit開放給我們使用,反映到Java代碼中就是---synchronized關鍵字,也就是說synchronized滿足原子性.
volatile
package passtra;
public class VolatileExample{
private static volatile int count=0;
public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10000;i++){
count++;
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(count);
}
}
開啟10個執行緒,每個執行緒都自加10000次,如果不出現執行緒安全的問題最終的結果應該是10*10000,可是運行多次都是小于100000,
問題在于volatile并不能保證原子性,count++并不是一個院子操作,包含了三個步驟:1、讀取變數count的值;2、對count加1;3、將新值賦給變數count,如果執行緒A讀取count到作業記憶體中,其他執行緒對這個值已經做了自增操作后,那么執行緒A的值自然而然就是一個過期的值,因此,總結過必然會是小于100000的,
如果讓volatile保證原子性,就必須符合以下兩個原則:
運算結果并不依賴變數的當前的值,或者能夠確保只有一個執行緒修改變數的值;
變數不需要與其他的狀態變數共同參與不變約束,
四、synchronized和volatile的有序性
synchronized
synchronized語意表示鎖在同一時刻只能由一個執行緒進行獲取,當鎖被占用后,其他執行緒只能等待,
因此,synchronized語意就要求執行緒在訪問讀寫操作共享變數時只能串行執行,因此synchronized具有有序性,
volatile
在JMM 中,為了性能優化,編譯器和處理器會進行指令重排序,也就是說Java程式天然的有序性,可以總結為:如果在本執行緒內觀察,所有的操作都是有序的,如果在一個執行緒觀察另一個執行緒,所有的操作都是無序的,
在單例模式的實作上有一種雙重檢驗鎖定的方式DCL(Double-checked Locking)
public class Singleton{ private static volatile Singleton singleton; private Singleton(){} public static Singleton getsingleton(){ if(singleton==null){ synchronized (Singleton.class) { if(singleton==null){ singleton=new Singleton(); } } } return singleton; } }
這里為什么要加volatile?先分析下不加volatile的情況,有問題的陳述句是這條:singleton=new Singleton();這條陳述句實際上包含了三個操作:1、分配物件的記憶體空間;2、初始化物件;3、設定singleton指向剛分配的記憶體地址,但由于存在重排序的問題,可能有以下的執行順序:如果2和3進行了重排序的話,執行緒B進行判斷if(singleton==null)時就會出現true,而實際撒花姑娘這個singleto并沒有初始化成功,顯而易見對B執行緒來說之后的操作就會是錯的,
而用volatile修飾的話就可以禁止2和3操重排序,從而避免這種情況
volatile包含禁止指令重排序的語意,其具有有序性,
五、synchronized和volatile的可見性
可見性是指:當一個執行緒修改了共享變數后,其他執行緒能夠立即得知這個修改,
synchronized:當執行緒獲取鎖時或從主記憶體中獲取共享變數的最新值,釋放鎖的時候會將共享變數同步到主記憶體中,所以,synchronized具有可見性
volatile:同樣在volatile分析中,會通過在指令中天機lock指令,一實作記憶體可見性,因此,volatile具有可見性
所以:synchronized具有原子性,有序性和可見性
volatile具有有序性和可見性
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/65089.html
標籤:Java
