欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单例模式

程序员文章站 2022-06-04 23:17:49
...

饿汉式

package Singleton;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;

//饿汉式单例模式
/*
* 饿汉式当类加载进内存时便会生成一个类对象,若类中定义了大容量数组,这样十分浪费空间
* 显然这种写法比较简单,但问题是无法做到延迟创建对象,
* 事实上如果该单例类涉及资源较多,创建比较耗时间时,
* 我们更希望它可以尽可能地延迟加载,从而减小初始化的负载,
* 反射和序列化会破坏此单例
*/
public class Hungryman implements Serializable {

    private static final long serialVersionUID = -5520679614965399284L;
    private static Hungryman instance = new Hungryman();

    private Hungryman(){}

    public static Hungryman getInstance(){
        return instance;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Hungryman instance = Hungryman.getInstance();
        Hungryman instance2 = Hungryman.getInstance();
        System.out.println(instance==instance2);
    }
}

懒汉式

package Singleton;

import java.lang.reflect.InvocationTargetException;

//懒汉式
/*
 * 这种写法能够在多线程中很好的工作避免同步问题,
 * 同时也具备lazy loading机制,遗憾的是,由于synchronized的存在,
 * 效率很低,在单线程的情景下,完全可以去掉synchronized,为了兼顾效率与性能问题
 * 反射和序列化会破坏此单例
 */
public class Lazyman {
    //volatile防止指令重排序
    private static volatile Lazyman instance;

    private Lazyman(){}

    //synchronized可以防止线程不安全
    public synchronized static Lazyman getInstance(){
        if(instance==null){
            instance = new Lazyman();
        }
        return instance;
    }
}

class Test1{
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Lazyman instance = Lazyman.getInstance();
        Lazyman instance1 = Lazyman.getInstance();
        System.out.println(instance.hashCode());
        System.out.println(instance1.hashCode());
    }

}

DCL懒汉式

package Singleton;
//DCL懒汉式(双重检查锁,进行了两次null检查)
/*
 * volatile防止指令重排
 * instance = new TestInstance();可以分解为3行伪代码
 * a.memory = allocate() //分配内存
 * b.ctorInstanc(memory) //初始化对象
 * c.instance = memory //设置instance指向刚分配的地址
 * 下面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。
 * 在多线程的情况下会出现以下问题。当线程A在执行instance=new DLCLazyman()时,B线程进来执行到if(instance==null)。
 * 假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。
 * 那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到return instance并返回一个未初始化的对象。
 * 反射和序列化会破坏此单例
 * */
public class DLCLazyman {

    private static volatile DLCLazyman instance;
    private DLCLazyman(){}

    public static DLCLazyman getInstance(){
        if(instance==null){
            synchronized (DLCLazyman.class){
                if(instance==null){
                    instance=new DLCLazyman();
                }
            }

        }
        return instance;
    }
}

class test2{
    public static void main(String[] args) {
        DLCLazyman instance = DLCLazyman.getInstance();
        DLCLazyman instance1 = DLCLazyman.getInstance();
        System.out.println(instance==instance1);
    }
}

静态内部类式

package Singleton;
//静态内部类单例模式
/*
* 静态内部类只会被加载一次,这种写法是线程安全的。
* 反射和序列化会破坏此单例
* */
public class Holder {
    private static class holder{
        private static  Holder instance = new Holder();
    }

    private Holder(){}

    public static Holder getInstance(){
        return holder.instance;
    }
}

class test3{
    public static void main(String[] args) {
        Holder instance = Holder.getInstance();
        Holder instance1 = Holder.getInstance();
        System.out.println(instance==instance1);
    }
}

如何解决序列化和反射破坏单例?

  1. 序列化可能会破坏单例模式,比较每次反序列化一个序列化的对象实例时都会创建一个新的实例,解决方案如下:
//测试例子(上述四种方式的解决方式类似)
public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     

   protected Singleton() {     
   }  

   //反序列时直接返回当前INSTANCE
   private Object readResolve() {     
            return INSTANCE;     
      }    
}   
  1. 使用反射获得私有构造器,解决方式可以修改构造器,让它在创建第二个实例的时候抛异常,如下:
public static Singleton INSTANCE = new Singleton();     
private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   
    }else{
        throw new RuntimeException("不要试图用反射破坏单例");
    }
}

枚举式

package Singleton;

import java.lang.reflect.InvocationTargetException;
//枚举单例
/*
 * 枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,
 * 在枚举类型的序列化和反序列化上,Java做了特殊的规定:
 * 在序列化时Java仅仅是将枚举对象的name属性输出到结果中,
 * 反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
 * 同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,
 * 从而保证了枚举实例的唯一性
 * 无法使用反射创建枚举实例,创建枚举实例只有编译器能够做到
 * */
public enum EnumSingleton{
    INSTAECS;
}

class test4{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleton instance = EnumSingleton.INSTAECS;
        EnumSingleton instaces1 = EnumSingleton.INSTAECS;
        System.out.println(instance==instaces1);

    }
}

相关标签: 23种设计模式