你所不知道的单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

1.单例模式的几种实现方式

所谓的单例必须要保证以下几点:

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

(1)饿汉式实现单例

代码如下:

package com.dangle.interview;
import java.io.Serializable;
public class SingleOne implements Serializable {
    private static  SingleOne singleOne=new SingleOne(); //创建私有自身对象
    private  SingleOne(){   //私有构造方法
    }
    public static SingleOne getIntanse(){   //提供该实例
        return  singleOne;
    }
    private Object readResolve(){  //支持反序列化
        return singleOne;
    }
}

饿汉式是线程安全的,但是反射是不安全的,反序列化也是不支持的但是我们加上

private Object readResolve(){  //支持反序列化
        return singleOne;
    }

这些代码就支持反序列化了。

  @Test
    public  void text2() throws  Exception{
        Class clazz=SingleOne.class;
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true); //暴力获取
        SingleOne s1=(SingleOne) declaredConstructor.newInstance();
        SingleOne s2 = SingleOne.getIntanse();
        System.out.println(s1);
        System.out.println(s2);
    }

结果:很明显不是一个对象。

com.dangle.interview.SingleOne@1060b431
com.dangle.interview.SingleOne@612679d6

(2)登记式实现单例

代码如下:

package com.dangle.interview;
/**
 * 登记式
 */
public class SingOneD implements Serializable{
    private  static  class Singoo{    //使用静态内部类的方式,创建一个私有的实例对象
        private  static  SingOneD sing=new SingOneD();
    }
    private  SingOneD(){ 
         system.out.ptintln("加载了") //私有构造方法
    }
    public  static SingOneD  getInstance(){  //提供外界获取方法
        return  Singoo.sing;
    }
    private Object readResolve(){  //支持反序列化
        return singleOne;
    }
}

使用内部类创建实例对象的时候会起到延时加载的作用,在加载SingOneD类的时候并不会创建实例对象,只有我们使用getInstance方法的时候,java才会加载内部类去生成实例对象。

延时加载的好处: 合理可以避免CPU和内存高峰。

登记式是线程安全的,反射是不安全的,但是反射我们可以防止和反序列化一样

 private  SingOneD() {
     system.out.ptintln("加载了") //私有构造方法
        if(Singoo.sing!=null){
            throw new RuntimeException("非法获取");
        }
    }

我们可以防止反射,当反射获取时抛出异常。同时饿汉式也可以通过这种方法防止反射。

(3)枚举式实现单例

代码如下:

package com.dangle.interview;
public enum  SingEnum {
    INSTANCE{    //定义实现方法    
        @Override
        protected  void doSomething(){
            System.out.println("do   something.......");
        }
    };
    protected    abstract void doSomething();
}

很明显枚举式防止反射,支持反序列化是非常高效的,但是枚举类不能继承是一个欠缺。

(4)懒汉式实现单例

代码如下:

package com.dangle.interview;

public class SingOneL {
    private  static  SingOneL singOneL=null;   //添加volatile关键字防止指令重排
    private  SingOneL(){

    }
    public static  SingOneL getStance(){       
                if(singOneL==null){
                    singOneL=new SingOneL();
                }                  
        return singOneL;
    }
}

很明显是线程不安全的,在并发问题下可以产生多个实体

修改:

 public static  SingOneL getStance(){    
     if(singOneL==null){
            synchronized (SingOneL.class){
                if(singOneL==null){
                    singOneL=new SingOneL();
                }
            }
     }
        return singOneL;
    }

双重验锁,提高性能。
singOneL=new SingOneL()会执行如下操作:
(1)分配对象内存空间
(2)初始化对象
(3)singOneL指向(1)中分配的空间

在默写编译器上会出现指令重排:
(1)分配对象内存空间
(2)singOneL指向(1)中分配的空间(此时对象并没有初始化)
(3)初始化对象
有非常小的概率会使获取对象实例为null,所以当我们添加volatile关键字的时候就会避免这种小概率的产生。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×