日常记录——多线程与高并发—volatile概念、功能、原理、举例、volatile与synchronized的区别

一、概念

volatile是一个特征修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

二、功能

1.保证数据可见性
2.禁止指令重排序
注意:不保证原子性
例如:修饰变量的++操作。

三.原理

1.保证数据可见性:依靠MESI(cpu的高速缓存协议),在java内存中,分为主内存和线程内存,主内存中记录变量的值,线程内存在获取变量的值时,先将值copy到自己的内存,执行本线程内操作后,再将变量值写回到主内存。线程之间的变量值的获取通过主内存来操作,那么问题就会产生,如果A线程获取到值修改还没回写到主内存,B从主内存获取A修改前的值,进行操作,那么就会在并发过程中不能保证变量值的准确性,发生脏读。
那MESI保证什么:当一个线程修改缓存中的字节时,服务器中其他线程会被通知,它们的缓存将视为无效。所以上面的问题就被绝解决了,当A线程修改了变量值,回通知B线程当前获取值无效,所以B会重新获取A线程回写到主内存的值。
MESI:CPU caceh (CPU 缓存) 中的 每个 caceh line (cpu 缓存中的单元)使用标值记录缓存状态

状态 含义 监听任务
M 修改 (Modified) 该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。 缓存行必须时刻监听所有试图读该缓存行相对就主存的操作,这种操作必须在缓存将该缓存行写回主存并将状态变成S(共享)状态之前被延迟执行。
E 独享(Exclusive) 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。 缓存行也必须监听其它缓存读主存中该缓存行的操作,一旦有这种操作,该缓存行需要变成S(共享)状态。
S 共享 (Shared) 该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。 缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)
I 无效 (Invalid) 该Cache line无效。

2.禁止指令重排:CPU为了提高执行效率,编译过后的指令在cpu会优化指令并发执行指令,但是在加上volatile关键字后,编译过后的指令在cpu会添加读屏障和写屏障,防止指令重排,并发执行安全。
例如单例模式下创建一个对象:

class Singleton {
	private static Singleton instance;
	private Singleton(){}
	public static Singleton getInstance() {
		if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
			synchronized (Singleton.class) {
				if ( instance == null ) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

instance = new Singleton();
这并不是一条原子性语句,他会分为三个步骤
memory = allocate(); //1:分配对象的内存空间
initInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上述的2、3步骤没有依赖的关系,3是依赖1的,经过指令重排并发执行的情况下,第1步执行过后,可能会出现先执行第3步,后执行第2步的情况。也就是说可能会出现instance变量还没初始化完成,其他线程就已经判断了该变量值不为null,返回fasle,导致结果返回了一个没有初始化对象的情况。而加上volatile关键字修饰后,可以保证instance变量的操作不会被JVM所重排序,每个线程都是按照上述一二三的步骤顺序的执行,保证对象的完整。

四、举例

1.`子线程一直循环判断flag变量是否为true,如果为fasle,线程执行结束。

public class volatiletest {
    public static /*volatile*/ boolean flag = true;

    public static void main(String[] args) {
        new Thread(() ->{
            System.out.println("线程开始");
            while (flag){

            }
            System.out.println("线程结束");
        }).start();
        try {
            Thread.sleep(1000);
            flag = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

但因为没有加volatile,主线程和子线程获取变量值不同,导致子线程一直运行中。
在这里插入图片描述
将volatile开启后,主线程之下子线程缓存一致,子线程在主线程睡1秒后结束执行。
在这里插入图片描述

五、volatile与synchronized的区别

1.Volatile只能修饰变量,synchronized只能修饰方法和语句块
2.保证可见性原理不同,volatile通过MESI,synchronized使用对象的监视器
3.synchronized可以保证原子性,volatile不能保证原子性
4.synchronized引起阻塞,volatile不会引起阻塞

猜你喜欢

转载自blog.csdn.net/weixin_43001336/article/details/106989052