zoukankan      html  css  js  c++  java
  • Java并发编程的艺术(二)——volatile、原子性

    什么是volatile

    Java语言允许线程访问共享变量,为了确保共享变量能够被准确一致地更新,如果一个字段被声明为volatile,那么Java内存模型将会确保所有线程看到这个变量时值是一致的。保证了多处理器开发中,共享变量的可见性。

    volatile的使用

    public volatile int num;
    

    volatile的实现原理

    由volatile修饰的共享变量进行写操作的时候,汇编代码中会多出一些操作,这些操作包括:

    • 将当前处理器缓存的数据写回到系统内存。
    • 这个写回的操作会使得其他处理器缓存的该内存地址无效。

    volatile的特性

    可见性

    什么是可见性

    内存可见性是指:一个线程修改一个变量的值后,其他的线程在访问这个变量的时候,就会立即得到修改后的值。即,一个线程对一条共享变量的修改,对其他线程可见。

    如果没有采用同步机制,那么共享变量对其他线程不会立即可见

    为什么会出现内存不可见的情况

    因为线程对变量有本地缓存,当开启线程的时候,系统会将共享内存中的数据拷贝到本地缓存,在线程结束前所有的操作都是基于本地缓存进行的,如果一个线程改变了一个共享变量,而其他线程本地缓存没有及时得到更新,操作的还是旧的值。

    这时候就需要同步机制来将修改的值同步到每个线程的本地缓存。

    如何确保共享变量的可见性

    对共享变量使用同步机制,在Java中可以:

    • 将共享变量用同步代码块包裹。
    • 用volatile修饰变量。

    为什么volatile可以保持共享变量的可见性

    用volatile修饰后的变量,在读写的时候,就多了一些操作:

    • 变量写:这个变量会直接写入共享内存,而不是线程的本地缓存空间。
    • 变量读:线程会从共享内存中读取这个变量,而不是从本地的缓存空间中读。

    volatile的额外功能

    当volatile变量进行写的时候,系统会将包括被修改的变量在内的所有线程本地缓存变量存到系统共享内存中。
    当volatile变量进行读的时候,系统也会将共享内存中所有的变量更新到线程的本地缓存。

    这就意味着,其他的普通变量在volatile变量之前被修改,那么,volatile变量被修改之后,这些变量也能被其他线程读到最新的值。

    原子性

    什么是原子性

    原子性是指一组操作连续地完成,中间不会被其他线程任务中断。

    volatile能确保long、double等8子节数据操作地原子性

    在32位的操作系统中,CPU一次只能读写32位的数据,由于long和double是64位的,所以它们的读写会进行两步。如果在多线程中,一个线程只操作了long、double的前面一部分,然后突然就有另一个线程进来操作这个变量,那么,得到的数据就是错的。

    可以利用volatile防止上述情况的发生,即操作long等数据的原子性。

    Java怎么实现原子操作

    使用循环CAS实现原子操作,基本思路就是循环进行CAS操作直到成功为止。1.5开始提供基本类的源自类。

    CAS操作的问题:

    1. ABA问题:虽然两次比较变量是相同的,但可能在中间时刻被修改成其他的值,又改回原值,这样就不能被知晓。在每次修改变量的时候追加版本号,CAS的时候一起比较版本号即可。
    2. 循环时间开销大:支持处理器提供的pause指令以提高执行效率。
    3. 只能保证一个共享变量的原子操作:可以把多个变量合并对比。

    重排序相关

    重排序是计算机系统为了提高程序的执行效率,改变代码执行顺序的一种行为。

    如果两个指令没有依赖关系,系统会对它们的顺序重新排序,但是如果变量被volatile修饰,那么重排序规则会相应发生变化:

    • volatile读:volatile变量的读操作前一行为volatile操作,那么这两行不会发生重排序;volatile读操作和它后一行代码不会发生重排序。
    • volatile写:volatile写操作与其前一行代码不会发生重排序;volatile写操作的后一行代码是volatile操作,那么这两行代码不会发生重排序。

    为什么需要这样的重排序规则呢?

    我认为是利用这个规则,实现进程间的通信。看下面这段代码:

    class VolatileExample {
    	int a = 0;
    	volatile boolean flag = false;
    	public void write() {
    		a = 1;
    		flag = true;
    	}
    
    	public void read() {
    		if(flag) {
    			int i = a;
    			...
    		}
    	}
    }
    

    线程A执行write之后,线程B执行read就能获得i被传递的值。就是应为在volatile读之前的数据不能跑到volatile后面,如果跑过去了,那想要传递的值就是看不见了。

  • 相关阅读:
    自动化测试框架搭建:Pytest+request+Allure
    免密登录
    Python中函数、方法的区别
    Python中的深浅拷贝
    Linux日志中如何查找关键字及其前后的信息
    kibana7.4.2配置文件
    Java消息队列——JMS概述
    Bootstrap学习(一):Bootstrap简介
    设计模式:简单工厂模式
    Java内存模型
  • 原文地址:https://www.cnblogs.com/lippon/p/14117677.html
Copyright © 2011-2022 走看看