Java面试题(三) JUC圣教之扒扒Volatile圣使的看家本领

一. Volatile

1. volatile是Java虚拟机提供的轻量级的同步机制
其特性:
	
	1.保证可见性
	
	2.不保证原子性
	
	3.禁止指令重排
2. 保证可见性
当多个线程访问主内存同一个变量时,一个线程修改了这个变量的值,
其他线程能够立即看到修改的值,并重新从主内存中取出修改后的变量。

代码演示:

public class VolatileDemo {

    public static void main(String[] args) {

        MyData myData = new MyData();

        //Lamda表达式
        //第1个线程
        new Thread(() -> {
            //模拟线程进入主内存拷贝变量副本
            System.out.println(Thread.currentThread().getName() + "\t come in");
            //休眠3秒,给其他线程去读主内存的变量
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //修改变量值
            myData.updateNum();
            System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
        }, "AAA").start();

        //第2个线程 main线程
        while (myData.num == 0) {
            //如果num不变,这个循环会一值执行,死循环,不会打印下面的那句话
            //如果没有人来告诉这个线程num已经被改了,他会一值执行下去
            //只有在num上加了Volatile线程才会有可见性,才会通知他去取新的num值,才退出循环
            //可见性也可以理解成是一种即时通信机制
        }

        System.out.println(Thread.currentThread().getName() + "\t mission is over num: " + myData.num);
    }
}

class MyData {
    //volatile修饰主内存变量num
    volatile int num = 0; //模拟主内存变量

    //模拟线程的工作内存修改变量
    public void updateNum() {
        this.num = 60;
    }
}

加了Volatile关键字的运行效果:
在这里插入图片描述
没加Volatile关键字的运行效果(死循环):
在这里插入图片描述

3. 不保证原子性
原子性:简称完整性,即某个线程正在做某个任务时,
中间不可以被影响,要么同时成功,要么同时失败。

代码演示:

public class VolatileDemo02 {

    public static void main(String[] args) {

        MyData01 myData = new MyData01();

        //Lamda表达式
        for (int i = 1; i <= 20; i++) {
            //20个线程
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    //每个线程执行加1000次
                    myData.addNum();
                }
            }, String.valueOf(i)).start();
        }

        //这个循环的作用就是等待上面线程全部执行完毕后主线程在动手
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        //执行完毕,使用main线程来输出结果
        System.out.println(Thread.currentThread().getName() + "\t finally num: " + myData.num);

    }
}
class MyData01 {
    //volatile修饰主内存变量num
    volatile int num = 0;

    public void addNum() {
        num++;
    }
}

运行结果:

数值随机,不能保证原子性。

在这里插入图片描述

4. 禁止指令重排
指令重排:在计算机执行程序时,为了提高性能,
编译器和处理器常常会对指令做重排,一般分以下三种:

在这里插入图片描述
处理器在进行重排顺序是必须要考虑指令之间的数据依赖性

Volatile是怎么禁止指令重排的呢?
内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:
	1.	保证特定操作的执行顺序
	
	2.	保证某些变量的内存可见性(利用该特性实现Volatile的内存可见性)

	3. 由于编译器和处理器都能执行指令重排优化,
	如果在其中插入Memory Barrier则会告诉编译器和CPU,
	不管什么指令都不能和这条Memory Barrier指令重排顺序,
	内存屏障另外一个作用是强制刷出各种CPU的缓存数据,
	因此任何CPU上的线程都能读取到这些数据的最新版本。

总结:Volatile实现了JMM(Java Memory Model)三大特性中两大特性,可见性和有序性,没有实现原子性。

猜你喜欢

转载自blog.csdn.net/w_x_A__l__l/article/details/106585904