Java语言提供了弱同步机制,即volatile变量,以确保变量的更新通知其他线程。volatile变量具备变量可见性、禁止重排序两种特性。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
引申
Java多线程的开发中有三种特性:原子性、可见性和有序性。
https://www.cnblogs.com/fulong133/p/12434577.html 这篇文章介绍三种特性
https://blog.csdn.net/u013412772/article/details/80109727
- 变量可见性
保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。 - 禁止重排序
volatile禁止了指令重排。比sychronized更轻量级的同步锁。在访问volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
![](https://i-blog.csdnimg.cn/blog_migrate/37ab99aeec47a6e126ebf340743587fe.jpeg)
当对非volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同CPU cache中。而声明变量是volatile的,JVM 保证了每次读变量都从内存中读,跳过CPU cache这一步。
- 适应场景
值得说明的是对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。
引申:
线程安全的两个方面:执行控制和内存可见。
执行控制的目的是控制代码执行(顺序)及是否可以并发执行。
内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。
https://blog.csdn.net/suifeng3051/article/details/52611233 介绍volatile和synchronize的区别
https://zhuanlan.zhihu.com/p/99240509 介绍volatile
volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。
https://www.zhihu.com/question/23944806 说明volatile不能保证原子性 只能保证可见性 以及 volatile在单例模式的应用
https://blog.csdn.net/qq_36551991/article/details/106542350 讲述了单例模式为什么要使用 synchronize+volatile的双重锁模式:synchronize保证原子性,volatile保证可见性、可序性。
在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前 的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就 可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数 据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行 读取。