java多線程---volatile
阿新 • • 發佈:2018-05-22
rgs 不同 void 出現 寫到 程序 sys 運行時 inter
被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現數據臟讀的現象。
當把變量聲明為volatile類型後,編譯器與運行時都會註意到這個變量是共享的,因此不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。
- 1.非volatile變量,讀寫時,每個線程從內存copy到cpu緩存中,多核cpu,每個線程在不同的cpu上被處理。變量會被copy到不同的cpu緩存中
- 2.volatile變量,每次從內存中讀,跳過了cpu緩存這一步。
- 特性
1.可見性。可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
2.有序性。禁止指令重排序優化。
eg
public class VolatileTest {
public static volatile int i = 0 ;
public static class VTTest implements Runnable{
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
i++; //可以理解為3步,1:讀到工作內存,2:進行+1計算 3:寫入新的i+1
System.out.println(Thread.currentThread().getId() + ":" + i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] t = new Thread[10];
for (int i = 0; i < 10; i++) {
t[i] = new Thread(new VTTest());
t[i].start();
}
for (int i = 0; i < 10; i++) {
t[i].join();
}
}
}
由於volatile保證了可見性,那麽在每個線程中對i自增完之後,在其他線程中都能看到修改後的值啊,所以有10個線程分別進行了1000次操作,那麽最終i的值應該是1000*10=10000。這裏面就有一個誤區了,volatile關鍵字能保證可見性沒有錯,但是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變量的操作的原子性。比如現在有2個線程,線程1開始讀取i到工作內存,進行+1操作,在線程1還沒有進行寫操作,線程2也開始讀取i到工作內存,進行+1操作,由於線程1還沒有寫操作,所以線程2的i還是有效的,線程1將遞增的結果寫到主內存,線程2讀取到工作內存中的i無效,但是線程2已經對i遞增完,也開始對i進行寫入到主內存操作,所以最終2個線程最終只對i進行一次遞增操作。那麽怎麽保證原子性呢,答案是加鎖。
eg
public class VolatileTest {
public static volatile int i = 0 ;
public static ReentrantLock lock = new ReentrantLock();
public static class VTTest implements Runnable{
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
lock.lock();
i++; //i = i + 1;非原子操作
System.out.println(Thread.currentThread().getId() + ":" + i);
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] t = new Thread[10];
for (int i = 0; i < 10; i++) {
t[i] = new Thread(new VTTest());
t[i].start();
}
for (int i = 0; i < 10; i++) {
t[i].join();
}
}
}
java多線程---volatile