synchronized可见性探讨
synchronized是jdk中的关键字,保证了原子性、可见性、有序性。本文主要探讨可见性的相关问题。可见性是指一个线程对共享变量的修改,是否对其他线程可见。JMM中规定了,lock操作会从主存中刷新最新共享变量的值到工作线程,而unlock会将工作线程中的值同步会主存。所以synchronized可以保证可见性。
在上一篇volatile修饰数组的实验二中,出现加了 System.out.println 方法,就能顺利刷新变量的值,我们进入这个方法,可以看到
其内部是加锁了的,锁住的是System中的常量,即 public final static PrintStream out; 。
为了方便讨论,我们再用一个例子说明一下问题:
public class VisibilityTest { static boolean flag = true; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (flag) { synchronized ("") { } } System.out.println("退出循环"); }).start(); Thread.sleep(200); new Thread(() -> flag = false).start(); } }
第一个线程不停访问flag的值,因为使用了synchronized,所以每次都能拿到最新的flag值,第二个线程没有使用加锁,不保证第一时间将flag的修改同步回主存,但不影响我们的测试,可以看到测试结果:
可以正常退出。
参考资料中提到,将""替换成new Objetc(),可见性就失效了。我们实验一下,修改线程1的代码如下:
new Thread(() -> { while (flag) { synchronized (new Object()) { } } System.out.println("退出循环"); }).start();
测试结果是无法退出循环。我怀疑是synchronized的优化,因为被用作锁对象的new Object(),是在第一个线程内部申明,所以JVM判定其他线程无法访问到这个锁对象,也就不存在竞争问题,这里的锁被消除了。为了证实我的想法,我们将锁对象提取出来
1 public class VisibilityTest { 2 3 static boolean flag = true; 4 static Object lock = new Object(); 5 6 public static void main(String[] args) throws InterruptedException { 7 new Thread(() -> { 8 while (flag) { 9 synchronized (lock) { 10 } 11 } 12 System.out.println("退出循环"); 13 }).start(); 14 Thread.sleep(200); 15 16 new Thread(() -> { 17 flag = false; 18 }).start(); 19 } 20 21 }
看行4,将锁对象定义为类变量,再次运行:
正常退出循环。
为什么字符串当锁就可以呢?因为不管是直接创建如 String a = "123" 还是创建字符串对象 String a = new String("123") ,都会在常量池创建常量,而常量池是可以共享的,所以JVM不会消除锁。
参考: