標籤:ring lib 任務 boolean volatile 代碼塊 thread 資料 private
當前的Java記憶體模型下,線程可以把變數儲存在本地記憶體(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變數的值,而另外一個線程還繼續使用它在寄存器中的變數值的拷貝,造成資料的不一致。
要解決這個問題,只需要像在本程式中的這樣,把該變數聲明為volatile(不穩定的)即可,這就指示JVM,這個變數是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共用的標誌都應該加volatile修飾。
Volatile修飾的成員變數在每次被線程訪問時,都強迫從共用記憶體中重讀該成員變數的值。而且,當成員變數發生變化時,強迫線程將變化值回寫到共用記憶體。這樣在任何時刻,兩個不同的線程總是看到某個成員變數的同一個值。
用volatile和不用volatile的區別,運行一下,就知道了。
不用volatile:
[java] view plain copy
- package com.keyword;
-
- public class TestWithoutVolatile {
- private static boolean bChanged;
-
- public static void main(String[] args) throws InterruptedException {
- new Thread() {
-
- @Override
- public void run() {
- for (;;) {
- if (bChanged == !bChanged) {
- System.out.println("!=");
- System.exit(0);
- }
- }
- }
- }.start();
- Thread.sleep(1);
- new Thread() {
-
- @Override
- public void run() {
- for (;;) {
- bChanged = !bChanged;
- }
- }
- }.start();
- }
-
- }
運行後,程式進入死迴圈了,一直在運行。
用volatile:
[java] view plain copy
- package com.keyword;
-
- public class TestWithVolatile {
- private static volatile boolean bChanged;
-
- public static void main(String[] args) throws InterruptedException {
- new Thread() {
-
- @Override
- public void run() {
- for (;;) {
- if (bChanged == !bChanged) {
- System.out.println("!=");
- System.exit(0);
- }
- }
- }
- }.start();
- Thread.sleep(1);
- new Thread() {
-
- @Override
- public void run() {
- for (;;) {
- bChanged = !bChanged;
- }
- }
- }.start();
- }
-
- }
程式輸出!=,然後馬上退出。
但是,很多情況下,用不用volatile,感覺不出什麼區別,什麼時候要用volatile呢?看看JDK裡使用volatile的類。
比如java.util.regex.Pattern裡的變數:
[java] view plain copy
- private transient volatile boolean compiled = false;
還有,java.lang.System的變數:
[java] view plain copy
- private static volatile Console cons = null;
一般就是初始化的時候,需要用到volatile。
java.util.Scanner裡的變數,如:
[java] view plain copy
- private static volatile Pattern boolPattern;
- private static volatile Pattern separatorPattern;
- private static volatile Pattern linePattern;
初始化boolPattern的代碼:
[java] view plain copy
- private static Pattern boolPattern() {
- Pattern bp = boolPattern;
- if (bp == null)
- boolPattern = bp = Pattern.compile(BOOLEAN_PATTERN,
- Pattern.CASE_INSENSITIVE);
- return bp;
- }
上面的情況,可以使用synchronized來對boolPattern加鎖,但是synchronized開銷比volatile大,volatile能夠勝任上面的工作。
volatile不保證原子操作,所以,很容易讀到髒資料。
使用建議:在兩個或者更多的線程訪問的成員變數上使用volatile。當要訪問的變數已在synchronized代碼塊中,或者為常量時,不必使用
Java中Volatile詳解