在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小。
Volatile的官方定义
Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
为什么要使用Volatile
Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。
内存可见性
由于 Java
内存模型( JMM
)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。
这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存。
如下图所示:
所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。
显然这肯定是会出问题的,因此 volatile
的作用出现了:
当一个变量被
volatile
修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
volatile
修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中。
本文主要针对 共享变量 可见性理解的演示。
代码片段1
public class T01_HelloVolatile {
/*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (running) {
}
System.out.println("m end!");
}
void n() {
System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
running = false;
System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
}
void f() {
System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
while (running) {
}
System.out.println("f end ,【running】的值为" + running);
}
public static void main(String[] args) throws InterruptedException {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m, "t1").start();
TimeUnit.SECONDS.sleep(5);
new Thread(t::n, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(t::f, "t3").start();
}
}
此段代码演示了 volatile .
- 先启动线程 m,m一直持有 running变量。
- 在启动线程n,n线程进行running属性的更改。
- 在启动线程f ,在f线程里面读取 running变量,验证是否可以拿到running修改后的变量。
以上代码输出结果:
m 修改【running】值前进行启动 ,【running】的值为true n 进行【running】的修改,修改前 【running】值为true n 进行【running】的修改,修改后 【running】值为false f 修改【running】值前进行启动 ,【running】的值为false f end ,【running】的值为false
结果显示 m 线程未结束,f线程结束
以上说明, 线程n修改了running值之后,之后的f线程来进行访问这个值的时候是能访问到修改的内容的。在线程n修改这个值之前启动的线程m是没法访问到线程n修改的这个值的内容的。
<a name="7WiiZ"></a>
#### 代码片段2
```java
public class T01_HelloVolatile {
/*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (running) {
// 此处千万不要 使用 System.out.println 此句代码会触发同步机制
}
System.out.println("m end!");
}
void n() {
System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
running = false;
System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
}
void f() {
System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
while (running) {
}
System.out.println("f end ,【running】的值为" + running);
}
public static void main(String[] args) throws InterruptedException {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(t::n, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(t::f, "t3").start();
}
}
此段代码比 代码片段1 中 m线程多了一段睡眠时间,加上 5 秒的睡眠时间之后,执行结果如下:
m 修改【running】值前进行启动 ,【running】的值为true
n 进行【running】的修改,修改前 【running】值为true
n 进行【running】的修改,修改后 【running】值为false
f 修改【running】值前进行启动 ,【running】的值为false
f end ,【running】的值为false
m end!
m 线程正常结束。 那么 片段1 中的总结 就不正确,应该正确理解为 一直持有的这个变量 通过volatile 修饰后,当其他线程修改这个变量之后,这个变量在当前线程就可见
代码片段3
public class T01_HelloVolatile {
volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (running) {
// 此处千万不要 使用 System.out.println 此句代码会触发同步机制
}
System.out.println("m end!");
}
void n() {
System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
running = false;
System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
}
void f() {
System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
while (running) {
}
System.out.println("f end ,【running】的值为" + running);
}
public static void main(String[] args) throws InterruptedException {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m, "t1").start();
TimeUnit.SECONDS.sleep(5);
new Thread(t::n, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(t::f, "t3").start();
}
}
此段代码和代码片段1 做比较因为多了 volatile 关键字,所以 m f 线程都能正常结束,和代码片段1 相比
来说明来 volatile 修饰的变量是可见的。