JMM
Java内存模型
原子性
通过锁 synchronized、ReentrantLock 来保证原子性
可见性
线程对主存的数据进行了修改,对另外的线程不可见。通过volatile来解决。
有序性
JVM会在不影响正确性的前提下调整语句的执行顺序,这种特性称之为指令重排,多线程下指令重排会影响正确性。
可见性 vs 原子性
System.out.println(); 方法中使用了 synchronized来保证变量的可见性。
synchronized (this) {
ensureOpen();
textOut.newLine();
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush)
out.flush();
}
volatile (易变关键字)
可以修饰成员变量和静态成员变量(局部变量线程私有,不共享,不能修饰),避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的最新值,线程操作volaile都是直接操作主存。
原理
可见性保证
有序性保证
DCL(double-checked locking)单例模式
public final class Singleton {
private volatile static Singleton INSTANCE = null;
private Singleton() { }
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
注意
- 单例类加 final :防止子类使用不当,覆盖父类方法,破坏单例
- 如果实现了序列化接口,需要实现 readResolve。
- 构造方法私有化,但不能防止反射创建新的实例
- 为什么不是将 INSTANCE 设置为 public,而是提供静态方法 —— 提供更好的封装性,实现懒加载,提供泛型支持。
synchronized 保证有序性的前提是共享变量全部交由 synchronized 来管理,不能和DCL一样留一部分在外边
因为变量之后有写屏障,所以调用构造方法的指令无法重排序到写屏障之后,从而变量在赋值之后之前的构造方法已经调用完成。
happens-before
包含以下规则: