• 单例模式的使用场景和常见实现方式
  • 发布于 2个月前
  • 178 热度
    0 评论
  • LoveC
  • 1 粉丝 34 篇博客
  •   
一.为什么需要单例模式?
1.节省内存和计算
2.保证结果正确
3.方便管理
使用场景:
单例模式是一种创建型设计模式,其目的是确保一个类只有一个实例,并提供一个全局的访问点。单例模式适用于以下场景:
1.Windows任务管理器:Windows任务管理器只能打开一个任务管理器实例。
2.窗口管理类:在GUI应用程序中,窗口管理类只需要一个实例来管理所有窗口。
3.数据库连接池:数据库连接池需要保证只有一个实例,以避免重复创建数据库连接。
4.日志类:应用程序的日志应用通常使用单例模式来确保只有一个日志记录器实例,以便在整个应用程序中记录日志。
5.线程池:线程池需要只有一个实例来方便对池中的线程进行控制。
这些场景中的共同点是它们需要全局使用的类进行频繁的创建和销毁,而单例模式可以避免这种情况的发生,从而节省开销。

二. 饿汉式(静态常量)—推荐指数:★★☆☆☆
优点:不会有线程安全问题。缺点:在类加载的时候就创建对象,如果一直没使用到该对象的话,就造成了内存浪费,如果对象初始化的工作有很多,也会影响到性能。

代码展示:
//饿汉式(静态常量) ---可用
publicclass Singleton1 {
    // 类加载时就创建该实例
    privatefinalstatic Singleton1 INSTANCE = new Singleton1();

    private Singleton1(){}

    public static Singleton1 getInstance(){
        return INSTANCE;
    }
}
三. 饿汉式(静态代码块) —推荐指数:★★☆☆☆
优缺点和第一种方式基本一致。
//堆代码 duidaima.com
//饿汉式(静态代码块) ---可用
publicclass Singleton2 {
    privatefinalstatic Singleton2 INSTANCE;
    // 以静态代码快的形式创建实例
    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2(){}

    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}
四. 懒汉式(线程不安全)—推荐指数:☆☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。缺点:有线程安全问题。
//懒汉试(线程不安全)---不可用
publicclass Singleton3 {

    privatestatic Singleton3 INSTANCE;

    private Singleton3(){}

    public static Singleton3 getInstance(){
        if (INSTANCE == null){
            // 当多个线程同时执行到这里,就会创建多个实例,那各自线程的实例就不是同一个了
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
}
五. 懒汉式(线程安全,同步方法)—推荐指数:★☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。缺点:没有线程安全问题,但是有很大的性能问题,当多个线程同时到达getInstance()方法时,需要排队进入。

这个是在第 3 步的基础上实现的,使用 synchronized 修饰静态方法,由于加上了同步工具类,同一时间只能有一个线程操作,也使得性能下降,所以也不推荐使用:
//懒汉试(线程安全)---不推荐
publicclass Singleton4 {

    privatestatic Singleton4 INSTANCE;

    private Singleton4(){}

    public synchronized static Singleton4 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }
}
六. 懒汉式(线程不安全,同步代码块)—推荐指数:☆☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。缺点:线程不安全,还有性能问题!多个线程在synchronized那一行排队,进入代码块后一样会创建多个对象。

这个是在第 4 步的基础上作进一步尝试,虽然性能上的问题解决了,但是又出现了线程不安全的问题,如下:
//懒汉试(线程不安全,同步代码块)---不可用
publicclass Singleton5 {

    privatestatic Singleton5 INSTANCE;

    private Singleton5(){}

    public static Singleton5 getInstance(){
        if (INSTANCE == null){
            // 如果多个线程同时执行到这里,依然会创建多个实例
            synchronized (Singleton5.class){
                INSTANCE = new Singleton5();
            }
        }
        return INSTANCE;
    }
}
七. 懒汉式(双重检查)—推荐指数:★★★★☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。缺点:复杂。
//堆代码 duidaima.com
//双重检查---推荐使用
publicclass Singleton6 {

    privatestatic Singleton6 INSTANCE;

    private Singleton6(){}

    public static Singleton6 getInstance(){
        if (INSTANCE == null){
            synchronized (Singleton6.class){
                //再做一次检查:由于同时只能有一个线程进入到这里,所以此时的 INSTANCE 如果还是 null,那么就再创建,创建之后有且仅有这一个实例
                if (INSTANCE == null){
                    INSTANCE = new Singleton6();
                }
            }
        }
        return INSTANCE;
    }
}
这个优化我们利用了双重检测机制和同步锁,这种方式也称为双重同步锁单例模式,但是这个案例还是线程不安全的,大家通过代码层面的分析后,发现确实不会有线程安全问题,那问题出现在哪呢?这个其实要和对象创建步骤和JVM 指令重排挂钩,我们正常创建对象的指令步骤是这样的:
memory = allocate() 分配对象的内存空间
ctorInstance() 初始化对象,执行对应的构造方法
instance = memory 设置instance指向刚分配的内存
但是因为JVM和cpu优化,发生了指令重排,执行顺序如下:
memory = allocate() 分配对象的内存空间
instance = memory 设置instance指向刚分配的内存
ctorInstance() 初始化对象
我们可以结合代码,假如A线程进入同步代码块执行 instance = new Singleton6(),执行到“instance = memory 设置instance指向刚分配的内存”,这个时候B线程在第一次执行“if (instance == null)”,发现instance不为空,直接返回instance实例,其实线程B得到的这个实例并没有完全初始化(A还没有执行完对象的初始化步骤)就已经使用了。

那如何禁止指令重排呢,很简单,用我们前面文章提到的volatile关键字就可以了,在 INSTANCE 前加上 volatile 关键字来修饰,代码如下:
//双重检查---推荐使用
publicclass Singleton6 {

    privatevolatilestatic Singleton6 INSTANCE;

    private Singleton6(){}

    public static Singleton6 getInstance(){
        if (INSTANCE == null){
            synchronized (Singleton6.class){
                //再做一次检查:由于同时只能有一个线程进入到这里,所以此时的 INSTANCE 如果还是 null,那么就再创建,创建之后有且仅有这一个实例
                if (INSTANCE == null){
                    INSTANCE = new Singleton6();
                }
            }
        }
        return INSTANCE;
    }
}
八. 静态内部类 — 推荐指数:★★★☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。缺点:没有太大缺点。
由于类在初始化时,并不会初始化静态内部类中的实例,所以这属于饿汉式单例:
//静态内部类 --- 推荐使用
publicclass Singleton7 {

    private Singleton7(){}

    privatestaticclass SingletonInstance{
        //JVM会保证构造方法的线程安全问题,即使多个线程同时访问 getInstance() 方法,也只会创建一个实例,这是 JVM 保证的 
        privatestaticfinal Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance(){
        return SingletonInstance.INSTANCE;
    }
}
九. 枚举 — 推荐指数:★★★★★
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。缺点:最优方案。
//枚举 --- 生产实践推荐使用
publicenum Singleton8 {
    INSTANCE;

    //方法
    public void whatever(){ }
}

使用的时候只需要调用Singleton8.INSTANCE ,比如这里想调用该类中的 whatever()方法,只需要执行 Singleton8.INSTANCE.whatever()
用户评论