Java基礎之線程安全

java多線程內存模型,主內存+工作內存。線程是將主內存的變量拷貝到工作內存中,在工作內存中對變量進行改動,再寫回到主內存中,這個過程包含lock、read、load、use、assign、store、write、unlock等幾個過程。jvm規定的這8個線程操作都是原子性的操作,但全過程並不是原子性的。jvm對這些操作做了限制,比如一個線程獲取共享變量的lock後,其它線程要獲取該變量的lock就必須等待前一個線程unlock操作,這保證了多線程操作共享變量時,共享變量的一致性。

但是jvm的這8種操作只是針對共享變量,而我們通常遇到的是要保證一塊代碼執行的原子性,jvm提供了monitorenter及monitorexit機制,它們就類似於變量的lock及unlock,對應代碼就是synchronized及java.util.cucurrent.locks包裏的Lock類。

Java對於多線程安全問題提供了多種解決方式。

1.volatile 關鍵字:

volatile修飾變量,他用於保證共享變量的可見性。volatile是jvm提供的最輕量級線程安全機制。它是用於修飾共享變量的

它的關鍵在於,每次寫操作都先於讀操作進行,即保證每次變量值的更改,對於之後其它線程的讀取是可見的。

volatile還有一個特點就是禁止指令重排。它的實現機制是,代碼編譯後,volatile變量出現的指令會被加上一個內存屏障,內存屏障的作用是保證下面兩點:

a).volatile變量的代碼行前面的代碼,在指令重排時永遠在volatile變量前;

b).volatile變量的代碼行後面的代碼,在指令重排時永遠在volatile變量之後。

volatile的應用場景比較少,下面舉個例子,展示volatile的一種應用場景。

共享變量

boolean f = false;

線程1

do something.........

f=true;

do something.........

線程2

do something.........

while(!f){

}

do something.........

如上,如果f不使用volatile修飾,則線程2就可能一直運行下去,陷入死循環,因爲線程1修改f變量後,線程2並沒有變。

這時候volatile關鍵字就保證了f修改對所有線程可見,線程2就不會陷入死循環。

上面情況是體現volatile保證可見性的特點。對於volatile放在指令重排的特徵,一般情況如下:

共享變量

boolean f=false;

線程1

do something.........

initSomeProp();//語句1

f=true;//語句2

do domething.........

線程2

do something.........

while(!f){

}

loadSomeProp();

上面例子是防止線程1的指令重排,導致線程2在loadSomeProp()時出錯。這是由於語句2有可能在執行時被jvm重新排到語句1之前進行,這導致線程1還沒有執行initSomeProp()方法,線程2就執行了loadSomeProp()方法,這是無法達到預期目標的。使用volatile就能夠防止語句2被重新排到語句1之前執行。

2.synchronized關鍵字:

java程序猿處理多線程問題時,關於線程安全,使用最多的就死synchronized關鍵字了。

synchronized關鍵字主要是用於保證一段代碼執行的安全性,它對一塊代碼加鎖,不需要手動釋放鎖。多線程執行synchronized代碼塊時,當一個線程獲取鎖,其它線程就只能等待鎖釋放後才能獲取鎖並執行代碼塊。

synchronized能夠保證多線程執行該代碼塊時線程間保持互斥性。因爲synchronized代碼塊在執行前或獲取lock,此時會從主內存中重新讀取所需的變量值,在代碼執行完成後會釋放鎖,將修改的變量值全部寫回主內存,所以synchronized也能保證變量的可見性。

synchronized可以用於修飾class、方法、方法中的一塊代碼。

用於修飾class時,表示該類的所有public方法均是線程安全的。其實鎖的對象是該類的對象本身。這種做法會使該類的所有public方法在多線程執行時均是互斥的,也會是不同方法之間的執行也是互斥的(即線程1執行方法1時,線程2想執行方法2也是不行的,因爲線程2要獲取同一個對象的鎖,而這個鎖正被線程1使用)。

用於修飾方法時,表示該方法是線程安全的。同樣的,如果該類中有多個方法都被synchronized關鍵字修飾了,則這些方法之間的執行也是線程互斥的,因爲修飾方法時,鎖的對象也是該類的對象本身。

用於修飾方法中的代碼塊時,需要一個參數,該參數即作爲鎖的對象,可以是this對象,也可以是該類中定義的成員變量。用成員變量做鎖對象時,一般不會選擇複雜的對象,一般都會定義一個空的byte數組對象。

3.java.util.concurrent.locks包:

這是jdk提供的工具包,裏面有許多Lock類,主要有ReentrantLock、ReentrantReadWritLock。提供了豐富的方法,以lock方法獲取鎖,以unlock方法釋放鎖。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章