在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小。

Volatile的官方定义

Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

为什么要使用Volatile

Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。

内存可见性

由于 Java 内存模型( JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。

这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存

如下图所示:
1、volatile可见性的理解 - 图1
所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。
显然这肯定是会出问题的,因此 volatile 的作用出现了:

当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。

volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中。

本文主要针对 共享变量 可见性理解的演示。

代码片段1

  1. public class T01_HelloVolatile {
  2. /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
  3. void m() {
  4. System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
  5. try {
  6. TimeUnit.SECONDS.sleep(1);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. while (running) {
  11. }
  12. System.out.println("m end!");
  13. }
  14. void n() {
  15. System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
  16. running = false;
  17. System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
  18. }
  19. void f() {
  20. System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
  21. while (running) {
  22. }
  23. System.out.println("f end ,【running】的值为" + running);
  24. }
  25. public static void main(String[] args) throws InterruptedException {
  26. T01_HelloVolatile t = new T01_HelloVolatile();
  27. new Thread(t::m, "t1").start();
  28. TimeUnit.SECONDS.sleep(5);
  29. new Thread(t::n, "t2").start();
  30. TimeUnit.SECONDS.sleep(1);
  31. new Thread(t::f, "t3").start();
  32. }
  33. }

此段代码演示了 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修改的这个值的内容的。

  1. <a name="7WiiZ"></a>
  2. #### 代码片段2
  3. ```java
  4. public class T01_HelloVolatile {
  5. /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
  6. void m() {
  7. System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
  8. try {
  9. TimeUnit.SECONDS.sleep(5);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. while (running) {
  14. // 此处千万不要 使用 System.out.println 此句代码会触发同步机制
  15. }
  16. System.out.println("m end!");
  17. }
  18. void n() {
  19. System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
  20. running = false;
  21. System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
  22. }
  23. void f() {
  24. System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
  25. while (running) {
  26. }
  27. System.out.println("f end ,【running】的值为" + running);
  28. }
  29. public static void main(String[] args) throws InterruptedException {
  30. T01_HelloVolatile t = new T01_HelloVolatile();
  31. new Thread(t::m, "t1").start();
  32. TimeUnit.SECONDS.sleep(1);
  33. new Thread(t::n, "t2").start();
  34. TimeUnit.SECONDS.sleep(1);
  35. new Thread(t::f, "t3").start();
  36. }
  37. }
  38. 此段代码比 代码片段1 中 m线程多了一段睡眠时间,加上 5 秒的睡眠时间之后,执行结果如下:
  39. m 修改【running】值前进行启动 ,【running】的值为true
  40. n 进行【running】的修改,修改前 【running】值为true
  41. n 进行【running】的修改,修改后 【running】值为false
  42. f 修改【running】值前进行启动 ,【running】的值为false
  43. f end ,【running】的值为false
  44. m end!
  45. m 线程正常结束。 那么 片段1 中的总结 就不正确,应该正确理解为 一直持有的这个变量 通过volatile 修饰后,当其他线程修改这个变量之后,这个变量在当前线程就可见

代码片段3

  1. public class T01_HelloVolatile {
  2. volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
  3. void m() {
  4. System.out.println("m 修改【running】值前进行启动 ,【running】的值为" + running);
  5. try {
  6. TimeUnit.SECONDS.sleep(1);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. while (running) {
  11. // 此处千万不要 使用 System.out.println 此句代码会触发同步机制
  12. }
  13. System.out.println("m end!");
  14. }
  15. void n() {
  16. System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
  17. running = false;
  18. System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
  19. }
  20. void f() {
  21. System.out.println("f 修改【running】值前进行启动 ,【running】的值为" + running);
  22. while (running) {
  23. }
  24. System.out.println("f end ,【running】的值为" + running);
  25. }
  26. public static void main(String[] args) throws InterruptedException {
  27. T01_HelloVolatile t = new T01_HelloVolatile();
  28. new Thread(t::m, "t1").start();
  29. TimeUnit.SECONDS.sleep(5);
  30. new Thread(t::n, "t2").start();
  31. TimeUnit.SECONDS.sleep(1);
  32. new Thread(t::f, "t3").start();
  33. }
  34. }
  35. 此段代码和代码片段1 做比较因为多了 volatile 关键字,所以 m f 线程都能正常结束,和代码片段1 相比
  36. 来说明来 volatile 修饰的变量是可见的。