美文网首页
Java自增原子性问题(测试Volatile、AtomicInt

Java自增原子性问题(测试Volatile、AtomicInt

作者: 错位的季节 | 来源:发表于2017-10-12 11:10 被阅读100次

    一、补充概念

    1.什么是线程安全性?

    《Java Concurrency in Practice》中有提到:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

    2.Java中的“同步”

    Java中的主要同步机制是关键字“synchronized”,它提供了一种独占的加锁方式,但“同步”这个术语还包括volatile类型的变量,显式锁(Explicit Lock)以及原子变量。

    3.原子性
     原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型)这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++;这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
    Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。
    代码示例:

     
    public class IncrementTestDemo {
    
        public static int count = 0;
        public static Counter counter = new Counter();
        public static AtomicInteger atomicInteger = new AtomicInteger(0);
        volatile public static int countVolatile = 0;
        
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread() {
                    public void run() {
                        for (int j = 0; j < 1000; j++) {
                            count++;
                            counter.increment();
                            atomicInteger.getAndIncrement();
                            countVolatile++;
                        }
                    }
                }.start();
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("static count: " + count);
            System.out.println("Counter: " + counter.getValue());
            System.out.println("AtomicInteger: " + atomicInteger.intValue());
            System.out.println("countVolatile: " + countVolatile);
        }
        
    }
    
    class Counter {
        private int value;
    
        public synchronized int getValue() {
            return value;
        }
    
        public synchronized int increment() {
            return ++value;
        }
    
        public synchronized int decrement() {
            return --value;
        }
    }
    

    输出结果

    static count: 9952
    Counter: 10000
    AtomicInteger: 10000
    countVolatile: 9979
    

    第一行与最后一行,每次运行将得到不同的结果,但是中间两行的结果相同。

    通过上面的例子说明,要解决自增操作在多线程环境下线程不安全的问题,可以选择使用Java提供的原子类,或者使用synchronized同步方法。

    而通过Volatile关键字,并不能解决非原子操作的线程安全性。

    java语言规范指出:为了获取最佳的运行速度,允许线程保留共享变量的副本,当这个线程进入或者离开同步代码块时,才与共享成员变量进行比对,如果有变化再更新共享成员变量。这样当多个线程同时访问一个共享变量时,可能会存在值不同步的现象。

    而volatile这个值的作用就是告诉VM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互。
    建议:当多个线程同时访问一个共享变量时,可以使用volatile,而当访问的变量已在synchronized代码块中时,不必使用。
    缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。

    相关文章

      网友评论

          本文标题:Java自增原子性问题(测试Volatile、AtomicInt

          本文链接:https://www.haomeiwen.com/subject/tgdnyxtx.html