浅谈单例模式

简介: 本文描述设计模式中的单例模式

单例模式用于确保一个类只有一个实例,并提供一个全局访问点。

一般使用场景

  1. 日志:单例日志记录器用于将消息记录到文件中。
  2. 数据库连接:单例数据库连接用于连接到数据库。
  3. 配置:单例配置对象用于存储应用程序配置。
  4. 缓存:单例缓存用于存储应用程序数据。

如何实现单例模式

public class Singleton {
    // data fields
    // ...
    private static final Singleton instance = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }

    // more public methods
   public void doSomething() {
   }
}

这意味着在加载类时创建一个静态单例实例。

这是最常见的实现,但它的内存效率不高。如果不使用类,单例仍然会被创建,如果单例很重,它将消耗大量内存。

让我们来看看创建单例时的一些重要问题。

  1. 线程安全:因为单例是为多个线程创建的,所以它们需要线程安全。线程安全可以从两方面来看待。

    1. 确保实例不会被创建吵过一次
    2. 如果单例对象保存数据,就像在缓存中一样,确保数据是线程安全的。更新数据的方法应该同步。
  2. 效率:我们需要确保最佳的内存使用和性能

    1. 内存泄漏:如果单例很重且不使用,它将消耗大量内存
    2. 资源使用:如果单例模式消耗系统资源,它将消耗大量的CPU周期。在这种情况下,除非使用单例,否则系统将得不到充分利用。
    3. 序列化和反序列化:如果单例被序列化和反序列化,它将被重新创建,在这种情况下将创建多个实例。然而,序列化单例并不常见,理解这一点很重要

所以让我们看看上述的单例实现是否考虑到了这些问题。

  1. 在实例创建方面是线程安全的——是的,因为在加载类时只创建一次静态实例。
  2. 内存使用不是最优的,因为单例是在加载类时创建的,而不是在使用类时创建的。
  3. 不会阻止单例对象被序列化和反序列化。

现在我们来逐个看看其他的方法。

私有静态实例与惰性初始化

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

更多的内存效率。

这意味着在第一次访问该单例对象之前,它不会被创建。

然而,这并没有考虑到其他问题,并且失去了线程安全性

私有静态实例与同步的getInstance()方法

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

更加有效,并且是线程安全的。

这解决了多个线程试图同时创建单例的问题。然而,这也有一个小小的权衡。同步方法会使单例模式变慢,因为如果另一个线程正在请求该单例模式,同步方法会阻塞调用线程。

带有双重检查锁定的私有静态实例

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

内存高效+线程安全。

这样更有效,因为同步块只进入一次,而不是在访问已经创建的实例时。

另一种实现方法是使用Singleton Holder模式。

单例持有人模式

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

注意,SingletonHolder.instance,当它第一次被调用时,SingletonHolder类被加载。

然后,类装入器将创建静态单例实例并返回它。

这使得SingletonHolder类是线程安全的,因为它持有一个静态实例。

上述所有实现的一个共同缺陷是它们都不能避免序列化或反射。

有两种方法可以防止这种情况:

实现readResolve ()

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Object readResolve() {
        return instance;
    }
}

通过实现readResolve()方法,该单例将被反序列化为已经创建的同一个实例。

枚举单例

public enum Singleton {
    INSTANCE;
    public void doSomething() {
    }
}

枚举只能有一定数量的实例/变体。这使得用枚举实现一个单例对象(只有一个可能的实例)成为可能。

枚举不受序列化和反射的影响。当一个enum被反序列化时,该实例将与已经存在的唯一可能的实例相同。

枚举实例化在设计上是线程安全的。

但是枚举单例也存在一定的问题,比如说内存效率问题。

哪个单例实现最适合你的用例?

  1. 如果内存不是问题,或者单例实例很轻,只需使用枚举单例。
  2. 如果内存是一个问题,使用惰性初始化单例。

    1. 此外,如果线程安全性存在问题,可以使用双重检查的锁单例或holder模式。
    2. 此外,如果你需要防止单例对象被序列化,可以使用readResolve()来双重检查锁定单例对象。
相关文章
|
4月前
|
存储 设计模式 测试技术
单例模式
单例模式
|
6月前
|
设计模式 安全 Java
单例模式分享
单例模式分享
30 0
|
7月前
|
安全 C++
C++单例模式
C++单例模式
55 1
|
7月前
|
设计模式 安全
【单例模式】—— 每天一点小知识
【单例模式】—— 每天一点小知识
|
设计模式 Java Spring
什么场景要使用单例模式,什么场景不能使用?
经常有小伙伴问我,设计模式学了这么久,每次看到概念也都能理解。但是,就是不知道怎么用,在哪里能用?我告诉大家,设计模式,不是为了要用而用的,而是作为前人总结下来的经验,等到哪天需要用的时候,你能想起来为你所用。
110 0
|
设计模式 缓存 Java
php设计模式-单例模式
php设计模式-单例模式
85 1
|
安全 Java
单例模式很简单
《基础系列》
123 0
单例模式很简单
|
设计模式 安全 前端开发
关于单例模式,你应该了解这些
关于单例模式,你应该了解这些
关于单例模式,你应该了解这些
|
设计模式 安全 Java
回顾一下单例模式
回顾一下单例模式
|
设计模式 缓存
我学会了,单例模式
单例模式属于创建型模式,这个类型的设计模式是将 对象的创建和使用解耦了,花式的去创建对象。
136 0
我学会了,单例模式