qiuyadong's Homepage

单例设计模式


设计模式中创建类模式,单例包含饿汉式、懒汉式、注册式、序列化。

饿汉式

概念:它是在类加载的时候就立即初始化,并且创建单例对象 优点:没有加任何的锁、执行效率比较高, 缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间,绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题;


public class Hungry {
    private Hungry(){}
    //先静态、后动态
    //先属性、后方法
    //先上后下
    private static final Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return  hungry;
    }
}

懒汉式

优点:用的时候加载,节省空间; 缺点:枷锁,性能差;


public class Lazy {
    private Lazy(){};
    private static Lazy instance=null;
    public static synchronized Lazy getInstance(){
            if(instance==null){
                instance=new Lazy();
            }
            return instance;
    }
}

补充:双重校验,像这样两次判空的机制叫做双重检测机制。

涉及到了JVM编译器的指令重排。

指令重排是什么意思呢?比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:

memory =allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance =memory; //3:设置instance指向刚分配的内存地址

但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:

memory =allocate(); //1:分配对象的内存空间

instance =memory; //3:设置instance指向刚分配的内存地址

ctorInstance(memory); //2:初始化对象

当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。 经过volatile的修饰,始终保证是下面的顺序: memory =allocate(); //1:分配对象的内存空间 ctorInstance(memory); //2:初始化对象 instance =memory; //3:设置instance指向刚分配的内存地址 如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。


public class Lazy {
    private Lazy(){};
    private static Lazy instance=null;
    public static  Lazy getInstance(){

            if(instance==null){
              synchronized(Lazy.class){
                  if(instance==null){
                    instance=new Lazy();
                }
              }

            return instance;
    }
}

懒汉式升级版

特点:在外部类被调用的时候内部类才会被加载,内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题 只有优点:这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题

public class LazyThree {

    private boolean initialized = false;
    //默认使用LazyThree的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyThree(){
        synchronized (LazyThree.class){
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("单例已被侵犯");
            }
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyThree getInstance(){
        //在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    private static class LazyHolder{
        private static final LazyThree LAZY = new LazyThree();
    }
}


注册式

Spring中的做法,就是用这种注册式单例


public class RegisterMap {

    private RegisterMap(){}

    private static Map<String,Object> register = new ConcurrentHashMap<String,Object>();

    public static RegisterMap getInstance(String name){
        if(name == null){
            name = RegisterMap.class.getName();
        }

        if(register.get(name) == null){
            try {
                register.put(name, new RegisterMap());
            }catch(Exception e){
                e.printStackTrace();
            }

        }
        return (RegisterMap)register.get(name);
    }


}

枚举式


public enum Color {
    RED(){
       private int r = 255;
       private int g = 0;
       private int b = 0;

    },BLACK(){
        private int r = 0;
        private int g = 0;
        private int b = 0;
    },WHITE(){
        private int r = 255;
        private int g = 255;
        private int b = 255;
    };
}

序列化式

主要防止反序列化时导致单例破坏


//反序列化时导致单例破坏
public class Seriable implements Serializable {

    //序列化就是说把内存中的状态通过转换成字节码的形式
    //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
    //内存中状态给永久保存下来了

    //反序列化
    //讲已经持久化的字节码内容,转换为IO流
    //通过IO流的读取,进而将读取的内容转换为Java对象
    //在转换过程中会重新创建对象new
    public  final static Seriable INSTANCE = new Seriable();
    private Seriable(){}
    public static  Seriable getInstance(){
        return INSTANCE;
    }
    private  Object readResolve(){
        return  INSTANCE;
    }
}

序列化单例测试代码


public class SeriableTest {
    public static void main(String[] args) {
        Seriable s1 = null;
        Seriable s2 = Seriable.getInstance();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("Seriable.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();


            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}



Comments