synchronized是jdk中的关键字,保证了原子性、可见性、有序性。本文主要探讨可见性的相关问题。可见性是指一个线程对共享变量的修改,是否对其他线程可见。JMM中规定了,lock操作会从主存中刷新最新共享变量的值到工作线程,而unlock会将工作线程中的值同步会主存。所以synchronized可以保证可见性。
在上一篇volatile修饰数组的实验二中,出现加了 System.out.println
其内部是加锁了的,锁住的是System中的常量,即 public final static
为了方便讨论,我们再用一个例子说明一下问题:
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
参考:
关于synchronized可见性的问题?
人生就像蒲公英,看似自由,其实身不由己。