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不会消除锁。

参考:

关于synchronized可见性的问题?

人生就像蒲公英,看似自由,其实身不由己。
原文地址:https://www.cnblogs.com/walker993/p/14867082.html