• volatile变量

在Java语言中,volatile变量提供了一种轻量级的同步机制,volatile变量用来确保将变量的更新操作通知到其它线程,volatile变量不会被缓存到寄存器或者对其它处理器不可见的地方,所以在读取volatile变量时总会返回最新写入的值,volatile变量通常用来表示某个状态标识。


  • 原子变量:

原子变量是“更强大的volatile”变量,从实现来看,每个原子变量类的value属性都是一个volatile变量,所以volatile变量的特性原子变量也有。同时,原子变量提供读、改、写的原子操作,更强大,更符合一般并发场景的需求。


既然原子变量更强大,是否还有必要使用volatile变量?如果有什么时候选择volatile变量,什么时候选择原子变量?当然这种选择只有在多线程并发的场景下才会出现,而多线程并发的目的一般是为了提高吞吐量和减少延迟响应,所以还是先看段测试代码和运行结果吧!

  1. import java.util.concurrent.CountDownLatch;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. public class TestVolatile {
  4.     private static int CALC_TIME = 1000;
  5.     private static final int THREAD_NUM = 100;
  6.     private AtomicInteger ai;
  7.     private int i;
  8.     private volatile int vi;
  9.                                                                                                                                                                                                           
  10.     public TestVolatile(){
  11.         ai = new AtomicInteger(0);
  12.         i = 0;
  13.         vi = 0;
  14.     }
  15.     public static void main(String[] args) throws InterruptedException {
  16.         System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
  17.         test();
  18.                                                                                                                                                                                                               
  19.         CALC_TIME = 10000;
  20.         System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
  21.         test();
  22.                                                                                                                                                                                                               
  23.         CALC_TIME = 100000;
  24.         System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
  25.         test();
  26.                                                                                                                                                                                                               
  27.         CALC_TIME = 1000000;
  28.         System.out.println("Calculation Times:" + CALC_TIME + " ----------------------");
  29.         test();
  30.     }
  31.     private static void test() throws InterruptedException {
  32.         testAi();
  33.                                                                                                                                                                                                               
  34.         testI();
  35.                                                                                                                                                                                                               
  36.         testVi();
  37.     }
  38.     private static void testAi() throws InterruptedException {
  39.         TestVolatile testVolatile = new TestVolatile();
  40.         CountDownLatch begSignal = new CountDownLatch(1);
  41.         CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
  42.         for (int i = 0; i < THREAD_NUM; i++) {       
  43.             new Thread( testVolatile.new WorkerAI(begSignal, endSignal) ).start();
  44.         }
  45.         long startTime = System.currentTimeMillis();
  46.                                                                                                                                                                                                               
  47.         begSignal.countDown();
  48.         endSignal.await();
  49.                                                                                                                                                                                                               
  50.         long endTime = System.currentTimeMillis();
  51.                                                                                                                                                                                                               
  52.         System.out.println("Total time consumed by atomic increment : " + (endTime-startTime));
  53.     }
  54.     private static void testI()
  55.             throws InterruptedException {
  56.         TestVolatile testVolatile = new TestVolatile();
  57.         CountDownLatch begSignal = new CountDownLatch(1);
  58.         CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
  59.                                                                                                                                                                                                               
  60.         for (int i = 0; i < THREAD_NUM; i++) {       
  61.             new Thread( testVolatile.new WorkerI(begSignal, endSignal) ).start();
  62.         }
  63.         long startTime = System.currentTimeMillis();
  64.                                                                                                                                                                                                               
  65.         begSignal.countDown();
  66.         endSignal.await();
  67.                                                                                                                                                                                                               
  68.         long endTime = System.currentTimeMillis();
  69.                                                                                                                                                                                                               
  70.         System.out.println("Total time consumed by synchronized increment : " + (endTime-startTime));
  71.     }
  72.                                                                                                                                                                                                           
  73.     private static void testVi()
  74.             throws InterruptedException {
  75.         TestVolatile testVolatile = new TestVolatile();
  76.         CountDownLatch begSignal = new CountDownLatch(1);
  77.         CountDownLatch endSignal = new CountDownLatch(THREAD_NUM);
  78.                                                                                                                                                                                                               
  79.         for (int i = 0; i < THREAD_NUM; i++) {       
  80.             new Thread( testVolatile.new WorkerVI(begSignal, endSignal) ).start();
  81.         }
  82.         long startTime = System.currentTimeMillis();
  83.                                                                                                                                                                                                               
  84.         begSignal.countDown();
  85.         endSignal.await();
  86.                                                                                                                                                                                                               
  87.         long endTime = System.currentTimeMillis();
  88.                                                                                                                                                                                                               
  89.         System.out.println("Total time consumed by volatile increment : " + (endTime-startTime));
  90.     }
  91.     public void incrAi() {
  92.         ai.getAndIncrement();
  93.     }
  94.     public synchronized void incrI() {
  95.         i++;
  96.     }
  97.     /**
  98.      * 这个函数不是线程安全,很可能得到错误的结果,这里只是为了测试读取volatile变量的效率
  99.      */
  100.     public void incrVi() {
  101.         vi++;
  102.     }
  103.     class WorkerAI implements Runnable {
  104.         private CountDownLatch beginSignal;
  105.         private CountDownLatch endSignal;
  106.         public WorkerAI(CountDownLatch begin, CountDownLatch end) {
  107.             this.beginSignal = begin;
  108.             this.endSignal = end;
  109.         }
  110.         @Override
  111.         public void run() {
  112.             try {
  113.                 beginSignal.await();
  114.             } catch (InterruptedException e) {
  115.                 e.printStackTrace();
  116.             }
  117.             for(int j=0; j<CALC_TIME; j++){
  118.                 incrAi();
  119.             }
  120.                                                                                                                                                                                                                   
  121.             endSignal.countDown();
  122.         }
  123.     }
  124.     class WorkerI implements Runnable {
  125.         private CountDownLatch beginSignal;
  126.         private CountDownLatch endSignal;
  127.         public WorkerI(CountDownLatch begin, CountDownLatch end) {
  128.             this.beginSignal = begin;
  129.             this.endSignal = end;
  130.         }
  131.         @Override
  132.         public void run() {
  133.             try {
  134.                 beginSignal.await();
  135.             } catch (InterruptedException e) {
  136.                 e.printStackTrace();
  137.             }
  138.             for(int j=0; j<CALC_TIME; j++){
  139.                 incrAi();
  140.             }       
  141.             endSignal.countDown();
  142.         }
  143.     }
  144.     class WorkerVI implements Runnable {
  145.         private CountDownLatch beginSignal;
  146.         private CountDownLatch endSignal;
  147.         public WorkerVI(CountDownLatch begin, CountDownLatch end) {
  148.             this.beginSignal = begin;
  149.             this.endSignal = end;
  150.         }
  151.         @Override
  152.         public void run() {
  153.             try {
  154.                 beginSignal.await();
  155.             } catch (InterruptedException e) {
  156.                 e.printStackTrace();
  157.             }
  158.             for(int j=0; j<CALC_TIME; j++){
  159.                 incrVi();
  160.             }
  161.             endSignal.countDown();
  162.         }
  163.     }
  164. }

程序运行结果:

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变量?专家推荐最佳实践是同时满足以下三个条件:

  1. 对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值

  2. 改变量不会与其他状态变量一起组成不变性的条件

  3. 在访问变量时不需要加锁


个人实践总结:

满足条件的情况下使用volatile布尔变量,其他数据类型使用原子变量。