什么是volatile?能保证原子性吗?


一、什么是volatile?

volatile是Java中一个关键字,用来修饰变量。可以用来保证多线程下访问的变量的可见性,但是不能保证原子性。

二、volatile如何保证可见性?

volatile翻译成不稳定的,一般我们的变量存在主内存里,但为了提高读写效率,每个线程都有自己的工作内存,存放着从主内存同步过来的变量副本。

volatile被比喻成轻量的synchronizedvolatile只能保证变量的可见性,而不能保证变量的原子性,synchronized能保证变量的可见性和原子性。

三、如何禁止指令重排?

volatile除了保证变量的可见性外,还能防止JVM的指令重排。

举个例子(典型的单例模式):

public class Singleton {
    private volatile static Singeton instance;
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        // 双重检验
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

实际上,instance = new Singleton()这一步是分为三步执行:

  1. instance分配内存
  2. 初始化instance
  3. 将·instance`指向分配的内存地址

但是由于JVM的指令重排,执行顺序可能变成1->3->2,这样子如果有另外一个线程来请求getInstance()方法,那么第一个instance == null将会返回true,但是此时instance还没初始化完,所以调用就会有问题。

四、volatile能保证原子性吗?

举个例子先,启动10个线程,每个线程执行10次自增操作,代码如下:

public class VolatileAtomicDemo {
    public volatile static int inc = 0;
    
    public static void increase() {
        inc++;
    }

    public static void main(String[] args) {
        ExecutorService thrreadPool = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++) {
            thrreadPool.execute(() -> {
                for(int j=0;j<10;j++) {
                    increase();
                }
            });
        }

        System.out.println("inc:" + inc);
    }
}

输出

inc:98

为什么inc不是100呢?不是说加了volatile就能保证变量的可见性了吗?

其实是因为inc++并不是原子性的,它的执行分为了三步:

  1. 读取inc的值
  2. 对inc加1
  3. 将inc的值写回内存

所以这个场景就可能会出现以下情况:

  1. 线程1读取到inc的值为0,还没来得及修改,线程2也读取到inc的值为0,并执行inc++后并写回内存(此时内存里inc的值为1)
  2. 这时线程开始执行inc++,并将结果值(1)写回内存(这时候就会出现内存中inc的值1被1覆盖,也就是2次++操作其实只生效了1次)

那么要如何解决这个问题呢?有以下三种方案:

  • 使用synchronized
  • 使用Lock
  • 使用AtomicInteger

使用synchronized

public synchronized int increase() {
    inc++;
}

使用AtomicInteger

public AtomicInteger inc = new AtomicInteger();

public void increase() {
    inc.getAndIncrement();
}

使用Lock

Lock lock = new ReentrantLock();
public void increase() {
    lock.lock();
    try {
        inc++;
    } finally {
        lock.unlock();
    }
}

五、Q&A

1、volatile和synchronized有什么区别?

volatilesynchronzied的区别如下:

  • 作用域volatile只能用于变量,synchronized能用于方法和代码块
  • 性能volatilesynchronized的轻量级实现,所以性能较好
  • 数据同步volatile只能保证数据的可见性,不能保证数据的原子性,synchronized能保证数据的可见性和原子性
  • 目的volatile主要用于解决变量在多个线程之间的可见性,synchronized主要用于解决多个线程之间访问资源的同步性

文章作者: GaryLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GaryLee !
  目录