Volatile关键字,你了解多少?

210 阅读2分钟

JVM我们很熟悉,那么JMM是什么呢?

JMM(Java Memory Model)定义了JVM在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

  • 记住JMM并不是真实存在的,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式

Volatile是Java虚拟机提供的轻量级的同步机制,Volatile三个特点:

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

当多个线程在操作成员变量的时候会造成数据被更改后,但是未及时刷新到主内存中,导致其它线程不可见,所以会存在问题,如下图:

JMM内存图.jpg

再来看看一段代码:

package com.tg01;

class Person {
    int age = 0;
    public void addAge() {
        age = 18;
    }
}

public class VolatileTest {
    public static void main(String[] args) {
        Person person = new Person();
        // 子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " ageAge前: " + person.age);
                // 模拟延时
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                person.addAge();
                System.out.println(Thread.currentThread().getName() + " ageAge后: " + person.age);
            }
        }, "子线程").start();

        // 主线程
        while (person.age == 0) {}
        System.out.println(Thread.currentThread().getName() + "MainThred over,age = " + person.age);
    }
}
  • 打印结果为:

  • 程序并没有结束,会一直在循环,为什么呢?原因很简单,是因为子线程在修改age的值之后,主线程并不知道,所以主线程就一直卡住了,那么这种问题我们怎么解决呢?
  • 有的人说加上synchronized同步代码块,这个当然可以,但是显得太笨重。
  • 这时候我们用Volatile关键字试试?
  • 在int age = 0,加上Volatile关键字,把代码修改如下:
package com.tg01;

class Person {
    volatile int age = 0;
    public void addAge() {
        age = 18;
    }
}

public class VolatileTest {
    public static void main(String[] args) {
        Person person = new Person();
        // 子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " ageAge前: " + person.age);
                // 模拟延时
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                person.addAge();
                System.out.println(Thread.currentThread().getName() + " ageAge后: " + person.age);
            }
        }, "子线程").start();

        // 主线程
        while (person.age == 0) {}
        System.out.println(Thread.currentThread().getName() + "MainThred over,age = " + person.age);
    }
}
  • 打印结果:

完美解决。

  • 但是这里要注意Volatile并保证原子性,比如int number,number++,可以修改成为AtomicInteger这个类操作即可。

那么Volatile还有哪些应用呢?

  • 单例模式
  • 禁止指令重排