一, 背景
最近在看这本书, 看到共享变量的可见性, 其中说到 "加锁的含义不仅仅局限于互斥行为, 还包括内存可见性".
我对于内存可见性第一反应是 volatile: 被 volatile 修饰的变量能够保证每个线程能够获取该变量的最新值, 从而避免出现数据脏读的现象.
原因是 volatile 修饰的共享变量进行写操作的时候会多出 Lock 前缀的指令, 通过多处理器的缓存一致性协议, 来保持变量的同步更新.
但是我却没明白 "加锁" 与 "可见性" 这句话表达的意思, 仔细思考下确实是这样子的.
二, 实践
public class NoSivibilityVariable {
private static boolean isOver = false;
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {@Override public void run() {
while (!isOver);
System.out.println("thread ----- true");
}
});
thread.start();
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
isOver = true;
System.out.println("main ----- true");
}
}
执行上面这段代码, 只会打印出 "main ----- true" 然后一直死循环阻塞. 原因是当主线程把变量 isOver 修改为 true, 值的修改作用范围仅仅是当前线程内 (主线程) 而另外的线程是主内存的值, 并没有读取主线程修改后的值, 所以另一个线程和主内存的值都是失效的值.
如果要解决这个情况怎么办?
1. 常见的做法就是把第二行 private static boolean isOver = false 修改为 private static volatile boolean isOver = false 就不会出现这种情况.
2. 就像书中所说的通过加锁来解决.
package synchronized_word;
public class NoSivibilityVariable {
private static boolean isOver = false;
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {@Override public void run() {
synchronized(NoSivibilityVariable.class) {
while (!isOver);
System.out.println("thread ----- true");
}
}
});
thread.start();
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
synchronized(NoSivibilityVariable.class) {
isOver = true;
}
System.out.println("main ----- true");
}
}
2 个线程中在对共享变量的读取或者写入都进行加锁处理, 因为线程对应的都是同一把锁对象 (该类对象) 所以相互会排斥. 但是就算这样子也不能说明内存可见性的. 其实真正解决这个问题的是 JMM 关于 Synchronized 的两条规定:
1, 线程解锁前, 必须把共享变量的最新值刷新到主内存中;
2, 线程加锁时, 讲清空工作内存中共享变量的值, 从而使用共享变量是需要从主内存中重新读取最新的值(加锁与解锁需要统一把锁)
线程执行互斥锁代码的过程:
1. 获得互斥锁
2. 清空工作内存
3. 从主内存拷贝最新变量副本到工作内存
4. 执行代码块
5. 将更改后的共享变量的值刷新到主内存中
6. 释放互斥锁
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#incorrectlySync
这里提到 synchronized 会保证对进入同一个监视器的线程保证可见性. 比如线程 t1 修改了变量, 退出监视器之前, 会把修改变量值 v1 刷新的主内存当中; 当线程 t2 进入这个监视器时, 如果有某个处理器缓存了变量 v1, 首先缓存失效, 然后必须重主内存重新加载变量值 v1(这点和 volatile 很像). 这里语义的解读只是说了对于同一个监视器, 变量的可见性有一定的方式可寻, 非同一个监视器就不保证了.
三, 总结
synchronized 具有内存可见性, 为了确保所有线程能够看到共享变量的值是最新的, 所有执行读操作或写操作的线程都必须在同一个锁上同步.
来源: https://www.cnblogs.com/hupu-jr/p/8397911.html