Java多线程编程(三)——volatile详解

        volatile关键字和synchronized一样都能够保证线程的同步。

        Java语言规范第三版中对volatile的定义如下:

       java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

        volatile被称为轻量级synchronized。同时,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。

一、volatile的特性

补充:

1. Java内存模型(Java Memory Model,JMM)

        JMM是由Java虚拟机规范定义的,用来屏蔽掉Java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。

        在Java中,Java堆内存是存在数据共享的,这些共享数据的通信就是通过JMM来控制的。

       JMM决定一个线程对共享数据的写入何时对另一个线程可见。

       JMM是一个抽象的结构,它定义了线程和主内存的关系

  • 线程之间的共享变量存储在主内存(Main Memory)
  • 每一个线程都有一个私有的本地内存(Local Memory)
  • 本地内存中储存了该线程可以读写变量的副本

扫描二维码关注公众号,回复: 8768659 查看本文章

        由此,我们可以得出如下结论

  • 只有存放在Java堆和方法区中的数据,才会被线程共享,对于其他区是属于线程私有的数据不受JMM的影响
  • 线程之间的数据,是不能直接进行数据传递的,一定要经过主内存进行传递
  • A线程更新数据 -> 刷新主内存数据 -> B线程读取主线程数据

       

       但是为什么需要内存模型?直接读写内存不可以吗?

       主要是因为下面两个原因:

    (1)CPU缓存一致性

        CPU与内存读写和运算速度不在一个量级,CPU效率会比内存高的多。

        为了解决CPU和内存效率差异问题,引入了高速缓存(Cache)和写缓冲区(Write Buffer)等,来作为CPU和内存的传输媒介。但是使用缓冲中读写可能造成数据不一致的问题,为了保证CPU缓存的一致性而引入了JMM。

    (2)处理器优化和指令重排

        处理器优化:处理器为了优化执行效率,可能会将输入的代码进行乱序执行处理
        指令重排:JIT编译过程也可能会对指令进行乱序处理

        为了解决这两个问题,需要引入JMM,而不是直接操作内存变量。

2. JMM并发的是三个特性:

(1)原子性

        表示不可被中断的一个或一系列操作。一旦开始,就一直运行到结束,中间不会有任何线程切换。

(2)可见性

        指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

(3)有序性

        由于指令的执行,会经过编译器和处理的重排序。有序性是指从指令上的执行结果上看,指令的执行顺序是有序的。

       有了上述补充的知识背景,下面我们看一下volatile具备哪些特性。

volatile的特性:

  • 互斥性:同一时刻只允许一个线程对变量进行操作。(互斥锁的特点)
  • 可见性:线程修改变量的值对其他线程立即可见。
  • 有序性:禁止指令的重排序。

       注:volatile不保证原子性,但是synchronized、lock可以保证。

           (synchronized具有互斥性、原子性、可见性,但是无法保证有序性。它无法保证编译器优化和指令重排)

       比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(synchronized)来让它变成一个原子操作。

       证明可见性代码:

public class VolatileTest {
    volatile int a = 1;
    volatile int b = 2;

    public void change(){
        a = 3;
        b = a;
    }

    public void print(){
        System.out.println("b="+b+";a="+a);
    }

    public static void main(String[] args) {
        while (true){
            final VolatileTest test = new VolatileTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}

       证明不保证原子性代码:

public class VolatileTest {
    volatile int i;

    public void addI(){  //修改 -> synchronized
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        final VolatileTest test = new VolatileTest();
        for (int n = 0; n < 1000; n++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.addI();
                }
            }).start();
        }
        Thread.sleep(10000);  //等待10秒,保证上面程序执行完成
        System.out.println(test.i);
    }
}

二、volatile的作用

  • 线程修改变量的值对其他线程立即可见
  • 禁止指令的重排序

三、volatile的原理

(1)可见性实现:

  • 修改volatile变量时,会强制将修改后的值刷新到主内存中
  • 修改volatile变量后,会导致其他线程工作内存中对应的变量值失效

(2)有序性实现:

       通过内存屏障对内存的操作顺序进行限制

四、volatile和synchronized的比较

  1. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好。
  2. volatile只能修饰变量,而synchronized可以修饰方法、代码块等。
  3. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
  4. volatile能保证数据的可见性,但不能保证数据的原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步处理。
  5. 关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
发布了35 篇原创文章 · 获赞 37 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/103969884