23种设计模式 --单例模式

x33g5p2x  于2022-03-16 转载在 其他  
字(4.4k)|赞(0)|评价(0)|浏览(215)

单例模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。单例模式有以下几种实现方式

  • 饿汉方式
  • 懒汉式—线程不安全:
  • 懒汉式—线程安全:
  • 双检锁式
  • 登记式/静态内部类
  • 枚举

饿汉式

将构造方法设置成私有方法,其他类无法new对象,从而保证只有一个实例。类加载到内存后,就实例化一个单例,JVM保证线程安全

  • 优点:简单实用(推荐使用)
  • 唯一缺点:不管是否用到,类加载时就会完成实例化对象
public class HungrySingleton {
    private final static HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton(){} //私有构造方法只能在本类中访问

    public static HungrySingleton getInstance() { //静态方法返回实例
        return INSTANCE;
    }

    public static void main(String[] args) {
        HungrySingleton hs1 = HungrySingleton.getInstance();
        HungrySingleton hs2 = HungrySingleton.getInstance();
        System.out.println(hs1==hs2);//true
    }
}

饿汉式的另一种写法

public class HungrySingleton{
    private final static HungrySingletonTwo INSTANCE;

    static { //只是将对象的初始化加到静态语句块中
        INSTANCE = new HungrySingletonTwo();
    }

    private HungrySingleton(){} //构造方法私有化

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

懒汉式—线程不安全

优点:需要用到实例才初始化实例
缺点:带来了线程不安全问题

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton(){}  //构造函数私有化

    public static LazySingleton getInstance() {
        if (instance == null) {
            //当一个线程走到这时,另一个线程判断为空也会实例化一个对象,就违背了单例模式
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) { e.printStackTrace(); }
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i <50 ; i++) {
            new Thread(()->{
                System.out.println(LazySingleton.getInstance().hashCode());//输出结果不一致
            }).start();
        }
    }
}

懒汉式—线程安全

懒汉式改进版(不推荐),通过Synchronized来解决线程不安全问题
缺点:虽然解决了线程安全问题,但是执行效率会降低

public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

双检锁式

懒汉式改进版,这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

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

登记式/静态内部类

静态内部类方式(完美写法之一)JVM保证单例,加载外部类时不会加载内部类,这样可以实现懒加载,但是这种单例模式可以被反射机制和反序列化破坏

public class InnerSingleton implements Serializable {

    private InnerSingleton(){}

    //静态内部类
    private static class InnerSingletonHolder{
        private final static InnerSingleton instance = new InnerSingleton();
    }

    public static InnerSingleton getInstance() {
        return InnerSingletonHolder.instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i <50 ; i++) {
            new Thread(()->{
                System.out.println(InnerSingleton.getInstance().hashCode());//输出结果一致
            }).start();
        }
    }
}

反射机制破坏单例模式解决方法

反射机制破坏单例模式代码

public class DestroyTest {
    public static void main(String[] args) {
        //获取字节码对象
        try {
            Class mgr = Class.forName("InnerSingleton");
            Constructor declaredConstructor = mgr.getDeclaredConstructor();//获取无参构造
            declaredConstructor.setAccessible(true);//取消访问检查
            //创建对象
            InnerSingleton m1 = (InnerSingleton)declaredConstructor.newInstance();
            InnerSingleton m2 = (InnerSingleton)declaredConstructor.newInstance();
            InnerSingleton m3 = (InnerSingleton)declaredConstructor.newInstance();
            System.out.println(m1);
            System.out.println(m2);
            System.out.println(m3);//输出内存地址不一样,说明m1、m2、m3不是同一个对象
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在InnerSingleton中判断是否已经存在了一个InnerSingleton对象,如果没有存在就new一个对象,如果已经存在就向外抛出异常

public static boolean isExist = false;
private InnerSingleton(){
	synchronized(InnerSingleton.class) {//保证在多线程环境下的线程安全问题
		if (isExist) {
			throw new RuntimeException("不能创建多个对象");
		} else {
		isExist = true;
		}
	}
}

反序列化破坏单例模式解决方法

反序列化破坏单例模式代码

public class DestroyTest {
    public static void main(String[] args) {
        try {
            writeToFile();
            readObj();
            readObj();//两次输出的内存地址不一样,说明破坏了单例模式
            //添加readResolve()后输出的结果一样,说明没有破坏单例模式
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void writeToFile() throws IOException { //向文件中写入对象
        InnerSingleton mgr = InnerSingleton.getInstance();//获取InnerSingleton 对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("C:\\Users\\uuu\\IdeaProjects\\DesignProject\\ch01-Singleton\\file\\a.txt"));
        out.writeObject(mgr);//写对象
        out.close(); //关闭资源
    }
   
    public static void readObj() throws Exception{ //向文件中读取对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("C:\\Users\\uuu\\IdeaProjects\\DesignProject\\ch01-Singleton\\file\\a.txt"));
        InnerSingleton mgr = (InnerSingleton)in.readObject();//读取对象
        System.out.println(mgr);//读取对象
        in.close();//释放资源
    }
}

在InnerSingleton中添加一个readResolve()方法,该方法在序列化时被反射机制调用, 如果定义了这一个方法就返回这个方法的返回值,否则就返回新new出来的对象

public Object readResolve(){
	return InnerSingletonHolder.instance;
}

枚举

枚举单例(完美写法之一)不仅可以保证单例,还可以防止序列化,java的反射机制可以通过Class文件newInstance反序列化一个对象,枚举类不会被反序列化的原因是枚举类没有构造方法

public enum EnumSingleton {

    INSTANCE;

    public void BusinessMethod(){ /*业务方法*/ }

    public static void main(String[] args) {
        for (int i = 0; i <50 ; i++) {
            new Thread(()->{
                System.out.println(EnumSingleton.INSTANCE.hashCode());//输出结果一致
            }).start();
        }
    }
}

相关文章