Volatile
作用:保证变量的可见性,有序性(禁止指令重排序)。不保证原子性。
如何保证可见性的?
场景:每个 线程 下都有一块 工作内存。要使用变量需要从 主内存 中把 变量 读取出来,使用完成后写入到主内存。如果这个时候在 工作内存 中修改,还没有来得及写入 主内存,其他的线程从主内存中读取又读取到旧的数据了,那么这个时候就是不可见的。
Volatile
是如何做的?
原理:
- 当对
volatile
变量进行 写操作 的时候,会立马将工作内存中的值写入到主内存中。 - 当对
volatile
变量进行 读操作 的时候,会将工作线程内的变量值置为无效,然后重新从主内存中获取。
禁止指令重排序
指令重排序 通常指的是 编译器 或 运行时环境 对程序代码中指令的顺序进行调整。
这个是编译时
Jvm
对代码的一个优化,他认为这部分命令重新排序后执行效率会更高。多核 CPU 也会对指令进行重排序以实现更好的并行处理。
volatile
作用:表示该变量的操作禁止重排序这种优化。使得指令可以按照编码顺序执行,避免在并行情况下的逻辑紊乱。
样例
public class VolatileDemo {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("Start");
while (flag) {
}
System.out.println("End");
}).start();
// 休眠一秒
TimeUnit.SECONDS.sleep(1);
flag = false;
}
}
程序一直没有结束,这就是主线程修改后其他线程不可见导致的。
加下 volatile
关键字后,能够正常结束了。
private static volatile boolean flag = true;
不加 Volatile 就不能保证可见性吗?
答案:不是!!!
还有那些情况可以让变量可见呢?
synchronized
互斥锁
原理:在获得互斥锁之后,会清除工作内存,重新从主内存中获取值到工作内存中,使用完成后将工作内存的值写到主内存,释放互斥锁。
修改样例
// 去掉 volatile
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("Start");
while (flag) {
// 增加打印日志
System.out.println(flag);
}
System.out.println("End");
}).start();
TimeUnit.SECONDS.sleep(1);
flag = false;
}
这样也能正常结束了。这是因为 System.out.println()
内部使用了 synchronized
关键字。
Sleep 休眠
让线程休眠触发
cpu
切换线程的机制,这样不可见的线程再重新唤醒后会重新从主内存中读取值
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("Start");
while (flag) {
try {
// 让不可见的线程休眠一下,cpu唤醒后会重新从主内存中读取值
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("End");
}).start();
TimeUnit.SECONDS.sleep(1);
flag = false;
}