美文网首页
volatile实现原理

volatile实现原理

作者: lois想当大佬 | 来源:发表于2020-04-30 12:02 被阅读0次

一、内存模型
Java内存模型分为主内存和线程工作内存两大类。
主内存:多个线程共享的内存。如下图所示,方法区和堆属于主内存区域。
线程工作内存:每个线程独享的内存。如下图所示,虚拟机栈、本地方法栈、程序计数器属于线程独享的工作内存。


jvm内存模型

如果想了解每个区域具体用途,可以查看相关文章:
jvm 内存划分
jvm 垃圾回收

二、特性(线程3要素)

  • 原子性:一个操作要么全部发生要么全部不发生,中途不能被打断。volatile 无法保证复合操作的原子性。
  • 可见性:其他线程可看到当前线程对变量的修改。使用volatile修饰的变量,任何线程对其进行操作都是在主内存中进行的,不会产生副本,从而保证共享变量的可见性。
  • 有序性:即程序执行的顺序按照代码的先后顺序执行。禁止指令重排。

三、为什么不具备原子性?
  volatile解决的是多线程共享变量可见性问题,但是被volatile修饰的变量操作并非具有原子性。
我们来看个例子:

public class TestVolatile {

    private static volatile int count = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for(int i=0; i<1000; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
            }
        });

        Thread thread2 = new Thread(() -> {
            for(int i=0; i<1000; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
            }
        });

        thread1.start();
        thread2.start();

        while (thread1.isAlive() || thread2.isAlive()) {}

        // 所有线程都执行完毕,则打印
        System.out.println(count);
    }
}

多次执行结果:
1879、1877……

上述代码两个线程同时执行count++操作1000次,多次执行结果均不为2000,可见被volatile修饰的变量操作不具有原子性。注:可以通过对count++加锁的方式或使用AtomicLong和LongAdder(JDK8推荐使用)类来实现count++的原子性。

synchronized实现原子性举例:

public class TestVolatile {

    private static Integer count = 0;

    private static void test() {
        synchronized (count) {
            count++;
        }
    }
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for(int i=0; i<1000; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                test();
            }
        });

        Thread thread2 = new Thread(() -> {
            for(int i=0; i<1000; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                test();
            }
        });

        thread1.start();
        thread2.start();

        while (thread1.isAlive() || thread2.isAlive()) {}

        // 所有线程都执行完毕,则打印
        System.out.println(count);
    }
}



四、volatile如何防止指令重排序
通过内存屏障来防止指令重排序,具体步骤:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。

volatile写内存屏障 volatile读内存屏障

保留问题:
volatile使用场景有哪些?

相关文章

网友评论

      本文标题:volatile实现原理

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