设计模式是什么?
模式就像是下棋的那个棋谱,打仗的那个兵法,说白了就是解决一些问题的固定套路。
今天我们主要讨论的是单例模式
什么是单例模式
顾名思义,单例就是某个类中只存在唯一一份实例,而不会创建出多个实例。
今天主要讨论两种单例模式
1.饿汉模式(急迫)
2.懒汉模式(从容)
【举个栗子:中午在家里吃完饭,妈妈让你去洗碗,你立刻就去,这是饿汉模式,你答应了但是一致没去洗,直到晚上吃饭没有碗的时候你才去洗,这是懒汉模式。】
饿汉模式
类加载的同时, 创建实例.
public class Dl {
/*
1.先将构造方法私有化, 避免用户直接在外部 new 新对象。
2.在类的内部属性部分直接创建一个对象(我们已经将构造方法私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象)。
3.向外提供一个公共的静态方法,用于外部引用该对象。(创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。)
注意:之所以用static是因为类外无法直接创建对象,我们加上static就可以通过类名获取该类的属性和方法了.
*/
//提供一个对象
private static Dl instance = new Dl();
//创建私有化的构造方法,防止用户在外部创建出新的实例
private Dl(){
}
//向外提供一个公共的静态方法,用于外部引用该对象。
public static Dl getInstance(){
return instance;
}
懒汉模式
在真正需要使用对象时才去创建该单例类对象【非必要,不加载】
/*
1.先将构造器私有化, 避免用户直接在外部 new 新对象。
2.在类的内部 声明一个对象引用,但是不创建, 即先不 new;
3.向外提供一个公共的静态方法,用于创建对象并返回对象引用。
(懒汉主要体现在不直接创建对象,而是在需要的时候再创建)
*/
class Singleton {
// 提供一个对象的引用
private static Singleton instance = null;
// 防止外部new对象
private Singleton(){}
// 用于创建对象或使得外部能够访问到创建的对象
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
两种实现方式的比较
两种实现方法都是通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(注意:事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。反射机制这块我们暂时不讨论)
线程安全问题的分析
饿汉式单例的实现仅仅只是读取了变量的内容,因此,是线程安全的!
但是以上懒汉式单例的实现没有考虑线程安全问题,(下图)这几行代码涉及到了读,写,而且不满足原子性,所以它是线程不安全的,并发环境下很可能出现多个Singleton实例.
那如何解决这个问题呢?
我们需要解决以下问题:
1.原子性[确保只有一个线程在操作]
2.内存可见性[编译器优化造成的]
3.指令重排序问题[多个线程调用同一个实例的情况下]
实现代码:
// 用于创建对象或使得外部能够访问到创建的对象
volatile public static Singleton getInstance(){
//用于判断是否加锁
if(instance == null) {
synchronized (Singleton.class) {
//用于判断是否创建对象实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
注意:
总结:
1.加锁, 把 if 和 new 变成原子操作
2.双重 if, 减少不必要的加锁操作
3.使用 volatile 禁止指令重排序,保证后续线程肯定拿到的是完整