volatile关键字的作用

volatile关键字的作用主要有以下两个作用

*内存可见性:当一个线程修改了一个共享变量时,另外一个线程能读到这个被修改的变量值。

*有序性:禁止指令重排序。

1、内存可见性

1.1、可见性问题

代码示例:


import java.util.Scanner;

/**
 * 观察内存可见性
 */
public class Exe_01 {
    public static class Counter{
        public static int count=0;
    }
    public static void main(String[] args) {
        Thread t1=new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"线程启动");
            while(Counter.count==0){
                //一直循环
            }
            System.out.println(Thread.currentThread().getName()+"线程退出");
        },"t1");
        //启动线程
        t1.start();
        //确保t1先启动
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread t2=new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"线程启动");
            //获取用户输入
            Scanner sc=new Scanner(System.in);
            System.out.println("请输入一个不为0的数字:");
            Counter.count=sc.nextInt();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程退出");
        },"t2");
        //启动线程
        t2.start();
    }
}

 volatile关键字的作用_第1张图片

 volatile关键字的作用_第2张图片

1.2、volatile解决内存可见性问题

添加volatile关键字修改代码


import java.util.Scanner;

/**
 * 观察内存可见性
 */
public class Exe_01 {
    public static class Counter{
        public static volatile int count=0;
    }
    public static void main(String[] args) {
        Thread t1=new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"线程启动");
            while(Counter.count==0){
                //一直循环
            }
            System.out.println(Thread.currentThread().getName()+"线程退出");
        },"t1");
        //启动线程
        t1.start();
        //确保t1先启动
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread t2=new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"线程启动");
            //获取用户输入
            Scanner sc=new Scanner(System.in);
            System.out.println("请输入一个不为0的数字:");
            Counter.count=sc.nextInt();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程退出");
        },"t2");
        //启动线程
        t2.start();
    }
}

 运行结果:

volatile关键字的作用_第3张图片

 volatile关键字的作用_第4张图片

2、有序性(指令执行的有序)

2.1、内存屏障

作用就是保证指令的执行先后顺序

内存屏障
屏障类型 指令示例 说明
LoadLoad Load1;LoadLoad;Load2 保证load1的读操作先于laod2执行
StoreStore Store1;StoreStore;Store2 保证Store1的写操作先于Store2执行,并刷新到主内存
LoadStore Load1;LoadStore;Store2 保证load1的读操作结束先于Store2的写操作执行
StoreLoad Store1;StoreLoad;Load2 保证store1的写操作已刷新到主内存之后,load2及其后的操作才能执行

1、在每个volatile写操作之前插入StoreStore屏障,这样就能让其他线程修改变量A之后,让修改的新变量对当前线程可见。

2、在写操作之后插入StoreLoad屏障,这样就能让其他线程获取变量A的时候,能够获取到已经被当前线程修改的值

3、在每个volatile读操作之前插入LoadLoad屏障,这样就能让当前线程获取变量A的时候,保证其它线程也能获取相同的值,这样线程读取到的数据就一样了。

4、在读操作之后插入LoadStore屏障,这样就能让当前线程在其它线程修改变量A的值之前,获取到主内存里面变量A的值。

注:

volatile可以保证内存可见性,是因为在编译的过程中,在volatile修饰的变量前后都加入了相关的内存屏障

volatile可以保证有序性。

3、原子性 

 代码示例


public class Demo_402 {
    private static volatile int num=50000;
    public static void main(String[] args) throws InterruptedException {
        Counter04 counter=new Counter04();
        //创建两个线程
        Thread thread1=new Thread(()->{
            for (int i = 0; i < num; i++) {
                //自增操作
                counter.increase();
            }
        });
        Thread thread2=new Thread(()->{
            for (int i = 0; i < num; i++) {
                //自增操作
                counter.increase1();
            }
        });
        //启动线程
        thread1.start();
        thread2.start();
        //等待线程结束
        thread1.join();
        thread2.join();
        //获取自增后的count值
        System.out.println("count结果="+counter.count);
    }
}

class Counter04{
    public int count=0;
    //执行自增操作
    public void increase(){
        //在方法之前加上synchronized,修饰来说明这是一个需要加锁的方法
        count++;
    }
    //方法不加锁
    public void increase1(){
        count++;
    }
}

 运行结果:

volatile关键字的作用_第5张图片

 volatile关键字不保证原子性,原子性是由synchronized保证的,在多线程环境中一般两个关键字搭配使用。

 4、总结

1、volatile通过内存屏障,保证内存可见性。

2、volatile通过内存屏障,禁止指令重排序,从而保证有序性。

3、volatile不保证原子性。

*通过内存屏障,当一个线程修改了一个共享变量的时候,后面的线程就必须从主内存中重新读取最新的值

*通过内存屏障,让相邻的两条指令串行执行,从而保证内存可见性。

注意:volatile没有别的用法,就只是修饰变量

你可能感兴趣的:(jvm,volatile)