漫画:什么是单例模式

image

 

public class Singleton {
    private Singleton() {}  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

我们来解释几个关键点:

  • 要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。

  • instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。

  • getInstance是获取单例对象的方法。

如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式,上面小新写的就是。

如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。代码如下:

 


public class EagerSingleton(){
 
    private static final EagerSingleton instance = new EagerSingleton();
 
    private EagerSingleton(){};
 
    public static EagerSingleton getInstance(){
 
       return instance;
    }
}

 

 


为了实现线程安全,我们一般采用双重检测机制,代码如下:

public class Singleton {
    private Singleton() {}  //私有构造函数
   private volatile static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}

我们来解释几个关键点:

1.为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

2.进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。

 

 

 

3、经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:

memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址

如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。

volatile的存在保证不发生指令重排序,从而使得其他线程访问一个未必初始化完成的类。

 

 

 

用静态内部类实现单例模式:

 

public class Singleton {
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

 

利用反射打破单例:

 

//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));

代码可以简单归纳为三个步骤:

第一步,获得单例类的构造器。

第二步,把构造器设置为可访问。

第三步,使用newInstance方法构造对象。

最后为了确认这两个对象是否真的是不同的对象,我们使用equals方法进行比较。毫无疑问,比较结果是false。

 

 

用枚举实现单例模式:

 

public enum SingletonEnum {
    INSTANCE;
}

最后总结一下:

  1. volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。

2.使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值