设计模式-单例模式(Singleton)

一、概念:

        单例模式是一种设计模式,保证一个类只有一个实例,并提供一个全局访问点。


二、实现模式:

        1、饿汉模式

                含义:顾名思义,当加载到该类的时候,就会去创建这个单例,而这个单例通常是以类级别的成员变量的形式出现。但是如果我不使用这个单例,那么就会造成内存浪费。

        2、懒汉模式

                含义:当需要真正用到这个单例的时候,才去创建它,一般是以getInstance()方法作为全局入口。   


三、代码实现:

        3.1、饿汉模式:

           3.1.1、 在全局变量中直接new

public class SingletonMode1 {
    //私有化构造方法
    private SingletonMode1(){

    }
    //定义成员变量,必须有static关键字
    private static SingletonMode1 singleton=new SingletonMode1();


    public static SingletonMode1 getInstance(){
        return  singleton;
    }
}
           3.1.2、在静态代码块中new对象。

        这里是利用当进行类加载时,静态代码块会被JVM执行且只被执行一次的特点。


public class SingletonMode2 {

    private SingletonMode2(){

    }
    private static SingletonMode2 singletonMode2;

    //静态代码块对类级别成员变量进行初始化
    static {
        singletonMode2=new SingletonMode2();
    }

    public static SingletonMode2 getInstance(){
        return singletonMode2;
    }
}

        3.2、懒汉模式:

                3.2.1、当调用该方法时,直接new对象

public class SingletonMode3 {
    private SingletonMode3() {
    }

    private static SingletonMode3 instance;

    /**
     * 当调用该方法时,直接new并且返回
     * @return
     */
    public static SingletonMode3 getInstance() {
          instance = new SingletonMode3();
          return instance;
    }


}

        但是这样在多线程环境下会出现问题,如果现在有十个线程都调用了这个方法,那么这些方法都创建了不同地址的单例对象,那么肯定是不行的,针对这个问题,我们可以想到上锁。


public class SingletonMode3 {
    private SingletonMode3() {
    }

    private static SingletonMode3 instance;

    /**
     * 调用该方法时,直接new并且返回 
     * @return
     */
    public static synchronized SingletonMode3 getInstance() {
          instance = new SingletonMode3();
          return instance;
    }


}

        但是synchronized加到方法上,肯定是力度太重了。因为当你是第一次访问这个方法,那么无可厚非你得上锁,但是当你第一次创建了之后,后面就不需要在去获取锁了,因为你只是读这个单例,而不是去写和修改这个单例的地址,这样就会导致后面想读这个单例的线程全都阻塞住了。针对这个问题,我们想到缩小synchronized的锁范围。


public class SingletonMode3 {
    private SingletonMode3() {
    }

    private static SingletonMode3 instance;

    /**
     * 双重检查锁用于实现多线程环境下的安全创建
     * @return
     */
    public static SingletonMode3 getInstance() {
        if (instance == null) {
            synchronized (SingletonMode3.class) {
                //第二层判断是为了排除当其他线程进入到第一个判断了之后,获取到锁,然后对该单例对象进行修改的情况。
                if (instance == null) {
                    instance = new SingletonMode3();
                }
            }
        }
        return instance;
    }

}

        但是,这么完美的代码依然存在一个问题:指令重排

        指令重排序是指:当JVM在保证最终结果正确的情况下,可以不按照程序编写的顺序执行语句,以尽可能提高程序的性能。

 创建一个对象,在JVM中会经过三步:

  1. 为对象分配内存空间
  2. 初始化对象
  3. 将对象指向分配好的内存

       在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1->3->2。这样就会导致多个线程获取到对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报空指针异常。

        解决方案就是:加上volatile 关键字


public class SingletonMode3 {
    private SingletonMode3() {
    }

    private static volatile SingletonMode3 instance;

    /**
     * 双重检查锁用于实现多线程环境下的安全创建
     * @return
     */
    public static SingletonMode3 getInstance() {
        if (instance == null) {
            synchronized (SingletonMode3.class) {
                //第二层判断是为了排除当其他线程进入到第一个判断了之后,获取到锁,然后对该单例对象进行修改的情况。
                if (instance == null) {
                    instance = new SingletonMode3();
                }
            }
        }
        return instance;
    }

}

volatile是可以保证有序性和可见性。

有序性:当程序执行的时候,会严格按照代码的先后顺序执行,防止指令重排序。

可见性:当一个线程修改了成员变量的值后,其他线程能够立即看到这个修改。

                  3.2.2、采用静态内部类

这里是利用当进行类加载时,静态代码块会被JVM执行且只被执行一次的特点。

/**
 * 使用静态内部类方法实现懒汉式单例实现
 */
public class SingletonMode4 {
    private SingletonMode4(){
       
    }
    /**
     * 用JVM的特性,加载外部类的时候,不会去加载静态内部类
     * 有自己的加载时机,独立于外部类
     */
    private static class SingletonMode4Holder {
        private static SingletonMode4 instance=new SingletonMode4();

    }
    public static SingletonMode4 getInstance(){
            return SingletonMode4Holder.instance;
    }


}

四、破坏单例模式的两种情况

        4.1、使用反射来破坏单例


public Class TestSingleton{
        
    public static void main(String[] args) throws Exception {
        //用正常入口获取单例
        SingletonMode4 newInstance1= SingletonMode4.getInstance();

        //使用反射,获取到类的构造方法来破坏单例
        Constructor<SingletonMode4> constructor = SingletonMode4.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonMode4 newInstance2 = constructor.newInstance();


        //输出结果是false
        System.out.println("newInstance1==newInstance2:"+(instance==newInstance1));

    }

}

        解决方法:直接在私有的构造方法里抛出异常,不让你创建新的对象。


public class SingletonMode4 {
    private SingletonMode4(){
        throw new RuntimeException("单例模式不能使用构造函数创建对象");
    }
    /**
     * 用JVM的特性,加载外部类的时候,不会去加载静态内部类
     * 有自己的加载时机,独立于外部类
     */
    private static class SingletonMode4Holder {
        private static SingletonMode4 instance=new SingletonMode4();

    }
    public static SingletonMode4 getInstance(){
            return SingletonMode4Holder.instance;
    }


}

        4.2、使用序列化和反序列化来破坏单例


public Class TestSingleton{
      public static void main(String[] args)throws Exception {
        //正常入口获取单例
        SingletonMode4 instance1 = SingletonMode4.getInstance();
        
        //先进行序列化,最后序列化破坏单例
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\DeskTop\\test.txt"));
        out.writeObject(instance1);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\DeskTop\\test.txt"));
        SingletonMode3 instance2 = (SingletonMode4) in.readObject();
        in.close();

        // 比较两个实例,输出是false
        System.out.println("newInstance1==newInstance2:"+(instance==newInstance1));

    }

}

        解决方法:在单例类中新增 readResolve()方法。


public class SingletonMode4 {
    private SingletonMode4(){
        throw new RuntimeException("单例模式不能使用构造函数创建对象");
    }
    /**
     * 用JVM的特性,加载外部类的时候,不会去加载静态内部类
     * 有自己的加载时机,独立于外部类
     */
    private static class SingletonMode4Holder {
        private static SingletonMode4 instance=new SingletonMode4();

    }
    public static SingletonMode4 getInstance(){
            return SingletonMode4Holder.instance;
    }
    
       /**
     * 解决方法就是将再该单例类中新增该方法
     * @return
     */
    private Object readResolve() {
        return SingletonMode4Holder.instance;  // 返回当前已存在的单例实例
    }


}

      简单点来说,就是在反序列化时,如果我们的类中有readResolve()方法,那么就会自动调用这个方法来实现反序列化,此时我们只需要返回一个已有的实例,从而避免生成新的对象。


五、总结

  1.  单例模式常见的写法有两种:懒汉式、饿汉式

  2. 类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题,但是可能会造成内存浪费。

  3. 懒汉式:在需要用到对象时才实例化对象。最佳实践:使用双重校验锁来实现单例的创建

  4. 为了防止多线程环境下,因为指令重排序导致变量报空指针异常,需要在单例对象上添加volatile关键字,防止指令重排序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值