1. 前言
- 在并发编程的过程中,
volatile
属性非常重要。- 首先我们要了解并发编程的三大特性:
可见性
,有序性
,原子性
- 而我们今天的了解的
volatile
就牵扯到可见性
,有序性
。- 同时我也会从个人了解的角度给大家分析下,如果有什么不对的地方也希望大家在评论区指正
2. 适合人群
- 线程的初学者
3. 可见性
大家可以先看一段代码,自己想想到底会有什么执行结果:
private static boolean flag = true;
public static void test01() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
Thread.sleep(2000);
flag = false;
}
也许有的人会给出答案说,执行2秒后,就会停止。但是事实会是这样吗?? 大家可以手动执行下。
3.1 内存可见性
针对上述的代码,既然已经提出来了结果肯定不会是正常结束的。那为什么会这样呢???
- 这里面会牵扯到线程缓存的事情。
- 当线程执行的时候,会将使用到的值进行拷贝,拷贝到线程的缓存中。所以我们修改值后其实修改的是主内存的值,跟线程的缓存的值无任何关系。所以就算你修改了值while循环也是不会停止的。 所以这个就是线程的可见性。最起码线程跟主线程之前的内存不可见的
- 所以我们的目的就是为了让"它"可见
3.2 volatile
属性
private static volatile boolean flag1 = true;
public static void test02() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (flag1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
Thread.sleep(1000);
flag1 = false;
}
- 大家可以运行上述的代码,实际执行的结果跟我们预想的是一致的。
- 为什么会出现这种情况呢??? 其实我们仔细观察就会发现 控制循环的变量,是被修饰符
volatile
进行修饰的。- 这意味着当线程用到变量
flag1
的时候,都会从主内存中查找,如果子线程修改了值,同样会通过刷新的形式同步到主内存中。所以这就体现了线程的可见性
3.3 volatile
修饰引用类型
static class A {
boolean running = true;
void m() {
System.out.println("程序开始...");
while (running) {}
System.out.println("程序结束...");
}
}
static private volatile A a = new A();
public static void test03() throws InterruptedException {
new Thread(a::m, "t1").start();
Thread.sleep(1000);
a.running = false;
}
看如上实例,结果是:实际的结果跟期望的结果不同,这是为什么呢。因为:
如果volatile修饰引用类型的话,引用类型本身可见,但是内部属性是不见的
3.4 我们只能用volatile
来修饰吗
public static void test04() throws InterruptedException {
Thread t1 = new Thread(() -> {
while (flag) {
try {
System.out.println("执行中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
Thread.sleep(1000);
flag = false;
}
- 通过上述的实例可以得知,其实这么写也是可以的正常执行的。那为什么呢???
- 其实因为我们添加了
System.out.println("执行中...");
. 这么神奇吗??? 接下来让我们看下println
源码的实现
通过上述的截图没看错,锁synchronized
也是设置线线程可见性的
4. 顺序性
你以为程序的执行是顺序的吗??? 不,理解错了不是你以为的就是你以为的。有时候 我们程序执行的时候是乱序的。
4.1 为什么是乱序的
- 什么情况下都可以乱序吗???
如果不影响单线程的最终一致性的话,都有可能进行乱序执行。执行的目的还是提高效率
volaitile
是如何阻止乱序的
- 每个实现jjvm底层的中间件,都必须实现JVM内存屏障
- 而
volatile
底层就是基于内存屏障来防止乱序的
5. 结束
- 上述的只是对
volatile
的使用场景,以及解决问题进行讲解。- 但是对乱序并没有做过多解释。第一呢,怕误人子弟,第二呢,感觉不是几个实例就能说清楚的。其实可以理解为就算
n++
这么一句话,翻译成JVM识别的语句,以及底层识别的语句都会转换成很多语句。在多线程的模式下,任何语句执行都会被打断,从而导致乱序执行。