volatile关键字

volatile

volatile是一个类型修饰符,作用是作为指令关键字,一般都是和const对应,确保本条指令不会被编译器的优化而忽略。
代码示例

int main()
{
	int i = 10;
	int a = i;

	printf("%d", i);

	//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
	__asm
	{
		mov dword ptr[ebp - 4], 20h
	}
	int b = i;
	printf("i=%d", b);
	return 0;
}

然后,在debug(调试)版本模式运行程序,输出结果如下:

i = 10 i = 32

然后在release版本模式运行下,输出结果如下:

i = 10 i = 10

输出结果表明一个问题,在release模式下,编译器对代码进行了优化,具体优化结果如下: 由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错

当用volatile修饰i后发现,在两个版本下i的值都为10

总结volatile可解释为“直接存取原始内存地址”。对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。

Java中的volatile

并发编程的三大特性

原子性、可见性和有序性。只要有一条原则没有被保证,就有可能会导致程序运行不正确。volatile关键字可被用来保证可见性,即保证共享变量的内存可见性以解决缓存一致性问题。一旦一个共享变量被 volatile关键字 修饰,那么就具备了两层语义:内存可见性和禁止进行指令重排序。

  • 原子性:一个或一组操作,要么全部执行,要么全部不执行。通常使用加锁实现。
  • 可见性:指多线程共享一个变量,其中一个线程改变了变量值,其他线程能够感知到变量被修改。
  • 有序性:程序执行顺序按照代码先后顺序执行。(禁止指令重排) ** 处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致(指令重排),但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。**

Java中volatile的作用

volatile是一个类型修饰符,JDK1.5之后,对其语义进行了增强。

  • 保证了不同线程之间对共享变量操作的可见性。
  • 通过禁止编译器、CPU指令重排序和部分hapens-before规则,解决有序性。

volatile保证可见性在JMM层面原理

volatile修饰的共享变量在执行写操作后,会立即刷回到主存,以供其它线程读取到最新的记录。

volatile保证可见性在CPU层面原理

使用 volatile 修饰的共享变量,底层通过汇编 lock 前缀指令进行缓存锁定,在线程修改完共享变量后写回主存,其他的 CPU 核心上运行的线程通过 CPU 总线嗅探机制会修改其共享变量为失效状态,读取时会重新从主内存中读取最新的数据。lock 前缀指令就相当于内存屏障

内存屏障(Memory Barrier 又称内存栅栏,是一个 CPU 指令)禁止重排序。

内存屏障的功能有三个:

  • 确保对内存的读-改-写操作原子执行
  • 阻止屏障两侧的指令重排序
  • 强制把缓存中的脏数据写回主内存,让缓存行中相应的数据失效

为什么单例模式中变量之前要加volatile

先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

分配内存空间。
初始化对象。
将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

分配内存空间。
将内存空间的地址赋值给对应的引用。
初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量

终止模式

停止标记用 volatile 是为了保证该变量在多个线程之间的可 见性

    class TwoPhaseTermination {
        // 监控线程
        private Thread monitor;
        // 停止标记
        private volatile boolean stop = false;;
        // 启动监控线程
        public void start() {
            monitor = new Thread(() -> {
                while (true) {
                    Thread thread = Thread.currentThread();
                    if (stop) {
                        System.out.println("后置处理");
                        break;
                    }
                    try {
                        Thread.sleep(1000);// 睡眠
                        System.out.println(thread.getName() + "执行监控记录");
                    } catch (InterruptedException e) {
                        System.out.println("被打断,退出睡眠");
                    }
                }
            });
            monitor.start();
        }
        // 停止监控线程
        public void stop() {
            stop = true;
            monitor.interrupt();// 让线程尽快退出Timed Waiting
        }
    }
    // 测试
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        System.out.println("停止监控");
        tpt.stop();
    }
end

评论