之前专门总结过单例模式,并且也稍微整理了一下voloatile关键字,这次专门结合单例总结理解一下volatile关键字。
一 Java内存模型(JMM)和指令重排序
JMM结构图及理解
1、在java内存模型中,共享变量放在主内存中,所有线程都可以共享访问,
2、每个线程都有自己独立的工作内存,用于存放当前线程的私有数据,线程与线程之间无法访问对方的工作内存空间
3、每个线程从主内存读取变量副本到自己的工作内存进行操作,操作完成后刷新回主内存
由于每个线程都是在自己的工作内存操作共享变量的副本,相互之间不可见,因此,当多个线程对同一共享变量进行操作时,单个线程对变量修改后刷新到主内存的值不会同步到其他线程的工作内存,造成变量数值不一致。
指令重排序
在执行程序时,为了提高性能,编译器和处理器在遵循as-if-serial原则的基础上,会对程序指令进行优化重排序。
如上述代码,2对1存在依赖关系,因此不会进行指令重排序,而3并不存在对1和2的依赖关系,因此会进行指令重排序,即实际执行顺序可能为:1 - 3 - 2
二 volatile关键字
volatile关键字特点
a.可见性
线程对变量的执行结果对其他线程可见。
用volatile关键字修饰的变量,当某一线程对其进行修改后,会执行以下操作:
1> 强制写回数值到主内存
2> 使其他线程工作内存中的变量副本失效,需重新从主内存读取
b.有序性
volatile可以禁止指令重排序,从而保证程序有序执行
volatile总结
-
volatile只能保证基本类型变量的内存可见性,对于引用类型,无法保证引用所指向的实际对象内部数据的内存可见性。
-
volilate只能保证共享对象的可见性,不能保证原子性:假设两个线程同时在做x++,在线程A修改共享变量从0到1的同时,线程B已经正在使用值为0的变量,所以这时候可见性已经无法发挥作用,线程B将其修改为1,所以最后结果是1而不是2。
三 单例模式(单例模式)
单例模式的特点
1> 只能有一个实例
2> 构造方法私有
3> 必须自行创建实例,并提供其他对象访问该实例的静态方法
基于以上原则,直接可实现以下单例类:饿汉式,类加载时,直接实例化,线程安全。缺点是如果该类用不到可能会造成资源浪费
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getSingleton(){ return instance; } }
由于上述方式可能会造成资源浪费,因此出现了懒汉式,即用到时进行实例化:获取实例时创建,多线程情况下会创建多个实例,线程不安全。
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getSingleton() { // 使用时创建,线程不安全 if (null == instance) { instance = new Singleton(); } return instance; } }
为了保证线程安全,最直接的方法就是在获取实例的方法上加上synchronized关键字,由于方法增加了同步锁,因此保证了线程安全,但因为锁定了整个方法,会非常影响效率。
public class Singleton { private static Singleton instance; private Singleton() { } /** * 增加synchronized关键字 * * @return */ public static synchronized Singleton getSingleton() { if (null == instance) { instance = new Singleton(); } return instance; } }
DCL双重校验锁,参考:https://blog.csdn.net/qq_26817225/article/details/107215878,增加volatile关键字,主要是考虑如果单例中有成员变量,指令重排序可能会造成成员变量线程不安全。
public class Singleton { /** * 增加volatile关键字,禁止指令重排序 */ private static volatile Singleton instance; private Singleton() { } public static Singleton getSingleton() { if (null == instance) { // 当实例为null,创建实例时,增加同步锁,并增加二次校验 synchronized (Singleton.class) { if (null == instance) { instance = new Singleton(); } } } return instance; } }