说起单例模式,很多人觉得很简单,不就那几行代码,没什么好理解,其实不然,单例模式的背后也会引出很多知识点。
1 饿汉式饿汉式实现步骤:
(1)私有构造方法
(2)声明静态变量
(3)对外提供静态方法
public class EagerSingleton { private final static EagerSingleton singleton = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return singleton; }}1.1 谈谈饿汉式单例模式为啥是线程安全的?
首先我们知道类加载过程有:加载、验证、准备、解析、初始化、使用和卸载。那么什么时候会触发这个类加载过程呢?
(1)new 关键字创建对象
(2)访问类的静态变量(除了被final修饰的静态变量)
(3)访问类的静态方法
(4)子类调用父类的静态变量,子类不会被初始化,只有父类会被初始化
其次 jvm 对类的加载也是通过一个个的线程来完成的,类的加载的过程也是线程安全的,可以理解
类加载的线程是互斥的。所以就能明白饿汉式单例模式为啥要通过静态变量的方式实现来提供实例。
2 懒汉式双重检查锁 volatile 关键字来实现
public class LazySingleton { private volatile static LazySingleton singleton = null; private LazySingleton() { } public static LazySingleton getInstance() { if (null != singleton) { synchronized (LazySingleton.class) { //这里为啥要加为空判断呢? //第一个线程释放锁创建了一个对象,第二线程进入时也创建了一个对象,相当于第一个对象被覆盖了 if (null != singleton) { //A线程刚好被指令重排序,就会先赋值,但还没有完成成员变量的初始化 singleton = new LazySingleton(); } } } return singleton; }}6.1.1 懒汉式单例模式存在的问题?
首先我们要理解一个对象在内存中创建的过程:LazySingleton s = new LazySingleton()
(1)new 关键字触发类加载机制,已被加载的类不需要再次被加载
(2)分配内存空间
(3)将对象进行初始化(即给类的成员变量赋默认值)
(4)将对象的应用地址指向栈内存中的变量
JVM的即时编译器会对代码的执行过程进行优化,包括代码的执行顺序。所以说上面创对象的过程可能被优化成 1 - 2 - 4 - 3 (指令重排序),这样导致对象创建的不完整,第二个线程来调用获得的对象去使用就会报错。以上的实现方式只保证了原子性、但未保证有序性、可见性问题
6.1.2 如何保证原子性、有序性和可见性
原子性字节码操作的原子性,比如 i 它不是原子性,为什么?jvm是如何操作的
(1)先从局部变量表中获取 i 的值
(2)将 i 的值加入到操作栈中
(3)将操作栈中的 i 进行自加
(4)将自己的 i 值放入到操作栈
(5)将操作栈的栈顶元素取出来并放到局部变量表中
怎么解决?使用 锁 机制来保证原子性
有序性int x;boolean y;x=10;y=false;此时 x和y 之间没有依赖性时,x和y的执行可以进行指令重排序。
int x=10;int y=x 1;此时 x和y 之间有依赖性时,不可以进行指令重排序。
怎么解决指令重排?volidate 关键字,被它修饰的变量不能指令重排序
可见性
线程A如果没有立即写入的主内存中,此时线程B读取到的 i 的值还是 10
怎么解决可见性问题?volidate 关键字,它可以禁止工作内存的使用,直接操作主内存
6.3 静态内部类
通过静态内部类也可以简单地实现单例模式,但这种方式没有太多可讲的知识点。
public class StaticInnerSingleton { private StaticInnerSingleton() { } /** * jvm 在类加载的时候是互斥,由此保证了线程安全的 */ private static class InnerSingleton { private static StaticInnerSingleton s = new StaticInnerSingleton(); } public static StaticInnerSingleton getInstance() { return InnerSingleton.s; }}关于单例模式,如何理解?
单例模式就是 :1、类的构造函数为private,即外部程序不能通过new关键字创建对象的实例
2、类中提供一个private static的 类变量引用 ;
3、单例类中提供静态方法 定义为 public static 的方法获取一个类的实例 ;
4、静态方法返回 类的引用,即 第2点中的 私有 静态变量 。
私有静态变量可以 定义的时候初始化 ,也可以 在第一次使用的时候,即调用AA.getInstanc()方法中判断 静态变量是否为空在初始化 。
关于单例模式,如何理解?
你的单例类一的引用cm和单例类二的引用cm2实际上都指向了类EagerSingleton中的privatestatic
final
EagerSingleton
hungryman这个唯一的实例(这便是特点一:单例类只可有一个实例
),即不管你创建再多的
EagerSingleton
cm3=EagerSingleton.getInstance();
EagerSingleton
cm4=EagerSingleton.getInstance();
......
其实cm3,cm4......他们都指向唯一的一个EagerSingleton类中的hungryman.即所有的单例类EagerSingleton的实例都是这个hungryman实例,明白了吗?