volatile變量
在Java語言中,volatile變量提供了一種輕量級的同步機制,volatile變量用來確保將變量的更新操作通知到其它線程,volatile變量不會被緩存到寄存器或者對其它處理器不可見的地方,所以在讀取volatile變量時總會返回最新寫入的值,volatile變量通常用來表示某個狀態標識。
原子變量:
原子變量是“更強大的volatile”變量,從實現來看,每個原子變量類的value屬性都是一個volatile變量,所以volatile變量的特性原子變量也有。同時,原子變量提供讀、改、寫的原子操作,更強大,更符合一般並發場景的需求。
既然原子變量更強大,是否還有必要使用volatile變量?如果有什麼時候選擇volatile變量,什麼時候選擇原子變量?當然這種選擇只有在多線程並發的場景下才會出現,而多線程並發的目的一般是為了提高吞吐量和減少延遲響應,所以還是先看段測試代碼和運行結果吧!
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class TestVolatile {
private static int CALC_TIME = 1000;
private static final int THREAD_NUM = 100;
private AtomicInteger ai;
private int i;
private volatile int vi;
public TestVolatile(){
ai = new AtomicInteger(0);
i = 0;
vi = 0;
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
test();
CALC_TIME = 10000;
System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
test();
CALC_TIME = 100000;
System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
test();
CALC_TIME = 1000000;
System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
test();
}
private static void test() throws InterruptedException {
testAi();
testI();
testVi();
}
private static void testAi() throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
CountDownLatch begSignal = new CountDownLatch(1);
CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread( testVolatile.new WorkerAI(begSignal, endSignal) ).start();
}
long startTime = System.currentTimeMillis();
begSignal.countDown();
endSignal.await();
long endTime = System.currentTimeMillis();
System.out.println("Total time consumed by atomic increment : " + (endTime-startTime));
}
private static void testI()
throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
CountDownLatch begSignal = new CountDownLatch(1);
CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread( testVolatile.new WorkerI(begSignal, endSignal) ).start();
}
long startTime = System.currentTimeMillis();
begSignal.countDown();
endSignal.await();
long endTime = System.currentTimeMillis();
System.out.println("Total time consumed by synchronized increment : " + (endTime-startTime));
}
private static void testVi()
throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
CountDownLatch begSignal = new CountDownLatch(1);
CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread( testVolatile.new WorkerVI(begSignal, endSignal) ).start();
}
long startTime = System.currentTimeMillis();
begSignal.countDown();
endSignal.await();
long endTime = System.currentTimeMillis();
System.out.println("Total time consumed by volatile increment : " + (endTime-startTime));
}
public void incrAi() {
ai.getAndIncrement();
}
public synchronized void incrI() {
i++;
}
/**
* 這個函數不是線程安全,很可能得到錯誤的結果,這裡只是為了測試讀取volatile變量的效率
*/
public void incrVi() {
vi++;
}
class WorkerAI implements Runnable {
private CountDownLatch beginSignal;
private CountDownLatch endSignal;
public WorkerAI(CountDownLatch begin, CountDownLatch end) {
this.beginSignal = begin;
this.endSignal = end;
}
@Override
public void run() {
try {
beginSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=0; j<CALC_TIME; j++){
incrAi();
}
endSignal.countDown();
}
}
class WorkerI implements Runnable {
private CountDownLatch beginSignal;
private CountDownLatch endSignal;
public WorkerI(CountDownLatch begin, CountDownLatch end) {
this.beginSignal = begin;
this.endSignal = end;
}
@Override
public void run() {
try {
beginSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=0; j<CALC_TIME; j++){
incrAi();
}
endSignal.countDown();
}
}
class WorkerVI implements Runnable {
private CountDownLatch beginSignal;
private CountDownLatch endSignal;
public WorkerVI(CountDownLatch begin, CountDownLatch end) {
this.beginSignal = begin;
this.endSignal = end;
}
@Override
public void run() {
try {
beginSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int j=0; j<CALC_TIME; j++){
incrVi();
}
endSignal.countDown();
}
}
}
程序運行結果:
Calculation Times:1000 ---------------------- Total time consumed by atomic increment : 8 Total time consumed by synchronized increment : 6 Total time consumed by volatile increment : 5 Calculation Times:10000 ---------------------- Total time consumed by atomic increment : 23 Total time consumed by synchronized increment : 24 Total time consumed by volatile increment : 15 Calculation Times:100000 ---------------------- Total time consumed by atomic increment : 354 Total time consumed by synchronized increment : 360 Total time consumed by volatile increment : 148 Calculation Times:1000000 ---------------------- Total time consumed by atomic increment : 3579 Total time consumed by synchronized increment : 3608 Total time consumed by volatile increment : 1519
(懷疑自己的程序寫得有問題,但暫時找不到問題,請大家幫忙拍磚!)
從測試結果看,原子變量的效率與synchronized同步操作效率差不多,感覺不到優勢,volatile變量提升一倍的性能(當然++操作是有同步問題),所以如果volatile變量能滿足需求優先使用volatile變量,原子變量次之。那什麼時候適合使用volatile變量?專家推薦最佳實踐是同時滿足以下三個條件:
對變量的寫入操作不依賴變量的當前值,或者能確保只有單個線程更新變量的值
改變量不會與其他狀態變量一起組成不變性的條件
在訪問變量時不需要加鎖
個人實踐總結:
滿足條件的情況下使用volatile布爾變量,其他數據類型使用原子變量。
出處:http://stevex.blog.51cto.com/4300375/1285964