searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

如何构造单例模式

2023-08-02 02:33:22
12
0

一、单例模式简介

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

二、单例实现方式

单例实现方式主要有四种:饿汉、懒汉、枚举类、静态内部类。

(1)饿汉式

// 问题1:为什么加 final
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
public final class Singleton implements Serializable {
// 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
private Singleton() {}
// 问题4:这样初始化是否能保证单例对象创建时的线程安全?
private static final Singleton INSTANCE = new Singleton();
// 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
public static Singleton getInstance() {
    return INSTANCE;
    }
public Object readResolve() {
    return INSTANCE;
    }
}
  • 问题1:防止单例类存在子类覆盖原来的单例方法,无法被继承。
  • 问题2:序列化使得能够通过反序列化来创建对象,而不用通过new方法,通过反序列化得到的对象不是原来的对象,破坏了单例,添加readResolve()方法来解决,反序列化会先调用该方法创建对象。
  • 问题3:构造函数只能够被本身所调用,而不public,但是不能够防止反射创建新的实例,反射能够获取类的加载器,直接创建对象。
  • 问题4:可以,static对象在类加载时就初始化,而类加载方法是线程安全的。
  • 问题5:static方法可以通过类名直接调用,而非静态方法需要先获得对象,矛盾;提供更好的封装性;能够重构为懒汉模式。

 (2)枚举

// 问题1:枚举单例是如何限制实例个数的
// 问题2:枚举单例在创建时是否有并发问题
// 问题3:枚举单例能否被反射破坏单例
// 问题4:枚举单例能否被反序列化破坏单例
// 问题5:枚举单例属于懒汉式还是饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton {
INSTANCE;
}
  • 问题1:isntance是enum中的静态成员变量,是唯一的。
  • 问题2:是静态变量在类加载时创建。
  • 问题3:反射不能破坏枚举单例。
  • 问题4:枚举已经实现了序列化接口,因此反序列化不会破坏单例。
  • 问题5:饿汉式。
  • 问题6:在枚举中添加构造方法和普通方法。

(3)懒汉式

public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;
// 分析这里的线程安全, 并说明有什么缺点
public static synchronized Singleton getInstance() {
if( INSTANCE != null ){
    return INSTANCE;
    }
INSTANCE = new Singleton();
    return INSTANCE;
    }
}
在getinstance方法上添加了synchronized关键字,可以保证线程安全,但锁的范围太大,每次调用getisntance方法都需要在类对象上加锁,效率低。

(4)(3)的改进,双重检查锁

public final class Singleton {
private Singleton() { }
// 问题1:解释为什么要加 volatile ?
private static volatile Singleton INSTANCE = null;
// 问题2:对比实现3, 说出这样做的意义
public static Singleton getInstance() {
    if (INSTANCE != null) {
        return INSTANCE;
        }
synchronized (Singleton.class) {
    // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗
    if (INSTANCE != null) {
        return INSTANCE;
            }
        INSTANCE = new Singleton();
        return INSTANCE;
        }
    }
}
  • 问题1:实现读写屏障,防止由于指令重排序造成未创建完成的单例对象逸出。
  • 问题2:通过缩小锁的范围,提高效率。
  • 问题3:当多个线程都进入getinstance方法,都执行完判断后就切换到其他线程,那么就会出现多个线程等待获得类对象锁,如果不判断,会出现多次创建对象,违反单例。

(5)静态内部类

public final class Singleton {
    private Singleton() { }
// 问题1:属于懒汉式还是饿汉式
    private static class LazyHolder {
    static final Singleton INSTANCE = new Singleton();
    }
// 问题2:在创建时是否有并发问题
public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
    }
}
  • 问题1:属于懒汉式,因为类的加载就是懒惰的,只有用到的类才会加载到jvm中。
  • 问题2:不会,类的加载由jvm自己来保证线程安全性,因此不会产生并发问题。

三、总结

单例的优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

应当根据不同的使用场景,使用不同的实现方式。

0条评论
作者已关闭评论
j****m
4文章数
0粉丝数
j****m
4 文章 | 0 粉丝
j****m
4文章数
0粉丝数
j****m
4 文章 | 0 粉丝
原创

如何构造单例模式

2023-08-02 02:33:22
12
0

一、单例模式简介

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

二、单例实现方式

单例实现方式主要有四种:饿汉、懒汉、枚举类、静态内部类。

(1)饿汉式

// 问题1:为什么加 final
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
public final class Singleton implements Serializable {
// 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
private Singleton() {}
// 问题4:这样初始化是否能保证单例对象创建时的线程安全?
private static final Singleton INSTANCE = new Singleton();
// 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
public static Singleton getInstance() {
    return INSTANCE;
    }
public Object readResolve() {
    return INSTANCE;
    }
}
  • 问题1:防止单例类存在子类覆盖原来的单例方法,无法被继承。
  • 问题2:序列化使得能够通过反序列化来创建对象,而不用通过new方法,通过反序列化得到的对象不是原来的对象,破坏了单例,添加readResolve()方法来解决,反序列化会先调用该方法创建对象。
  • 问题3:构造函数只能够被本身所调用,而不public,但是不能够防止反射创建新的实例,反射能够获取类的加载器,直接创建对象。
  • 问题4:可以,static对象在类加载时就初始化,而类加载方法是线程安全的。
  • 问题5:static方法可以通过类名直接调用,而非静态方法需要先获得对象,矛盾;提供更好的封装性;能够重构为懒汉模式。

 (2)枚举

// 问题1:枚举单例是如何限制实例个数的
// 问题2:枚举单例在创建时是否有并发问题
// 问题3:枚举单例能否被反射破坏单例
// 问题4:枚举单例能否被反序列化破坏单例
// 问题5:枚举单例属于懒汉式还是饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton {
INSTANCE;
}
  • 问题1:isntance是enum中的静态成员变量,是唯一的。
  • 问题2:是静态变量在类加载时创建。
  • 问题3:反射不能破坏枚举单例。
  • 问题4:枚举已经实现了序列化接口,因此反序列化不会破坏单例。
  • 问题5:饿汉式。
  • 问题6:在枚举中添加构造方法和普通方法。

(3)懒汉式

public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;
// 分析这里的线程安全, 并说明有什么缺点
public static synchronized Singleton getInstance() {
if( INSTANCE != null ){
    return INSTANCE;
    }
INSTANCE = new Singleton();
    return INSTANCE;
    }
}
在getinstance方法上添加了synchronized关键字,可以保证线程安全,但锁的范围太大,每次调用getisntance方法都需要在类对象上加锁,效率低。

(4)(3)的改进,双重检查锁

public final class Singleton {
private Singleton() { }
// 问题1:解释为什么要加 volatile ?
private static volatile Singleton INSTANCE = null;
// 问题2:对比实现3, 说出这样做的意义
public static Singleton getInstance() {
    if (INSTANCE != null) {
        return INSTANCE;
        }
synchronized (Singleton.class) {
    // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗
    if (INSTANCE != null) {
        return INSTANCE;
            }
        INSTANCE = new Singleton();
        return INSTANCE;
        }
    }
}
  • 问题1:实现读写屏障,防止由于指令重排序造成未创建完成的单例对象逸出。
  • 问题2:通过缩小锁的范围,提高效率。
  • 问题3:当多个线程都进入getinstance方法,都执行完判断后就切换到其他线程,那么就会出现多个线程等待获得类对象锁,如果不判断,会出现多次创建对象,违反单例。

(5)静态内部类

public final class Singleton {
    private Singleton() { }
// 问题1:属于懒汉式还是饿汉式
    private static class LazyHolder {
    static final Singleton INSTANCE = new Singleton();
    }
// 问题2:在创建时是否有并发问题
public static Singleton getInstance() {
    return LazyHolder.INSTANCE;
    }
}
  • 问题1:属于懒汉式,因为类的加载就是懒惰的,只有用到的类才会加载到jvm中。
  • 问题2:不会,类的加载由jvm自己来保证线程安全性,因此不会产生并发问题。

三、总结

单例的优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

应当根据不同的使用场景,使用不同的实现方式。

文章来自个人专栏
myjavatest
4 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
1
1