volatile不保证原子性,但是保证了可见性、顺序性。
synchronized保证原子性、可见性、顺序性。
以下面代码为例:
public class StudyAVolatile {
final static int MAX = 5;
static volatile int init_value = 0; // 这里加入volatile才能正确运行
public static void main(String[] args) {
new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
if (init_value != localValue) {
System.out.println("初始值变化为" + init_value);
localValue = init_value;
}
}
}, "Reader").start();
new Thread(() -> {
int localValue = init_value;
while (localValue < MAX) {
System.out.println("现在修改初始值为" + (++localValue));
init_value = localValue;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "Updater").start();
}
}
在main中,我们启动了两个线程,分别是Reader和Updater。后者负责更新变量init_value
的值。如果我们在注释的那一行,不加入volatile,那么Reader就无法做到监测到init_value
的变化,这一个线程将不会输出任何内容。
使用了volatile,才让二者真正共用了“init_value
”这个变量。
volatile不会使线程进入阻塞,而synchronized会使线程进入阻塞。
volatile的作用:
保证了多个线程之间,对共享变量操作的可见性。也就是一个线程修改volatile修饰的变量时,别的线程也会立即看到最新的值。
2023年3月18日补充
都是个人的思考
为什么要考虑顺序性呢?
因为①编译的时候可能会对代码进行优化,优化后的结果,并不一定是代码书写的顺序;②CPU运行的时候也会指令重排,可能会让后面的指令先运行,并不是严格地按照指令的顺序运行。使用volatile修饰可以避免指令重排。为什么要考虑可见性呢?
现代 CPU 和内存(主存)之间并不是之间相连的,中间有 cache 或者其它缓存。代码里使用某一个变量时,CPU 并不一定是从内存里取出来这个变量的值,而是有可能从缓存里取出来。有时候内存里的变量变了,缓存里还没有来得及变化。这是没有实现可见性的。用volatile修饰可以实现可见性。
volatile可以在读的指令、写的指令前后加上内存屏障,及既保证了顺序性,又保证了可见性。不过我们也知道volatile其实没上锁,不能保证原子性。