当前位置:硬件测评 > 设计模式(三)——单例模式

设计模式(三)——单例模式

  • 发布:2023-10-01 20:06

什么是单例模式

在实际开发中为了节省系统资源,有时需要保证系统中某个实例只有一个实例,而这种模式就是我们常见的单例模式。单例模式一般有三个关键点:

  • 单例类只有一个实例
  • 实例自行创建
  • 需要自己将这个实例提供给整个系统

代码实现

实例模式相比其他模式实现起来相对简单。比如现在我们的系统需要创建一个独特的文件管理器(FileManager):

公共  文件管理器 {
    私有 静态 文件管理器实例;

    公共 文件管理器() {
    }

    公共 静态 文件管理器getInstance (){
        if(实例 == null){
            实例 =  FileManager();
        }
        返回 实例;
    }


    公共 静态 void main(字符串[] args) {
        FileManager fileManager1 = FileManager.getInstance();
        FileManager fileManager2 = FileManager.getInstance();
        System.out.println(fileManager1 == fileManager2);
    }
}

通过上面的代码我们很简单的就实现了单例模式,从运行结果来看可以发现fileManager1fileManager2就是同一个实例。

线程安全

上面的代码看似没有问题,但实际上还是有很大的问题。上面的代码在单线程的情况下不会有问题,但在多线程的情况下就不一定了。多线程的情况下,很可能多个线程在判断instance == null时会同时判断true。这将导致多个线程进入实例。 = new FileManager(),导致单例模式失败。那么出现这种情况该如何解决呢?

使用synchronized来修改
 公共 静态 同步 FileManager getInstanceWithSyncLock  (){
   if(实例== ){
                                                                                                                                                                                                                      因为       返回 实例;
   }

这是通过添加同步锁来保证线程安全的最简单的方法,但是这种方法的问题是会有性能的损失,在实际开发中并不是最好的解决方案。

双重检查锁定

此前,多线程下使用同步的方式来解决单例线程安全问题,但出于性能原因并不推荐使用。事实上,同步方法的问题在于每次获取实例时都需要添加同步锁。即使实例已经成功创建,仍然需要使用同步锁,这会导致性能不佳。我们可以通过使用双重检查锁定来很好地解决这个问题。修改代码如下:

 公共 静态 文件管理器 getInstanceWithDCL(){
if(实例 == null){
                                   已同步(文件管理器){
                                                                                           ​|

不过,需要注意的一点是,对于实例实例,我们需要使用易失性来修改它,以避免指令排序的负面影响。使用双锁可以解决实例创建后无锁获取单个实例的问题,也可以保证线程安全。与之前的方法相比,性能有所提高。

饿汉式单身汉

我们上面实现的单例模式通常被称为惰性单例。它的特点是只有当我们获得单例时才会触发单例的实例化。如果我们从不调用它,系统将不会创建实例。 。这种惰性单例的负面影响就是存在线程安全问题,需要使用同步锁来解决安全问题。与懒惰式单例相反的是饥饿式单例。它的特点是,无论你使用与否,它都已被创建。

公共  单例 {
私有 静态 单例实例 = 单例 (); | | main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
       Singleton singleton2 = Singleton.getInstance(); }

从代码实现可以看出,这种方法不存在线程安全问题,而且代码实现也非常简单。但这种方法的缺点是,即使你整个系统没有人获得实例,这个实例仍然会被创建。与懒惰单例相比,饥饿风格会占用更多的系统资源。

初始化需求支架

之前介绍的懒惰式和饥饿式单例各有优缺点。有没有办法同时实现无锁和延迟加载?答案是有点,这个方法我们通常称之为初始化需求持有者(IoDH)。我们直接看代码看看是如何实现的。代码如下:

公共  IoDHS单例 {

    私有 IoDHSingleton(){}

    私有 静态  HolderClass{
        私人 静态 final IoDHSingleton 实例 = new IoDHSingleton();
    }

    public  静态 IoDHS单例getInstance() {
        返回 HolderClass.instance;
    }

    public  静态 空白 主要(String[] args) {
        IoDHSingleton instance1 = IoDHSingleton.getInstance();
        IoDHSingleton instance2 = IoDHSingleton.getInstance();
        System.out.println(instance1 ==实例2);
    }
}

通过代码可以发现,该方法在单例类内部添加了一个静态内部类,在内部类中创建了一个单例对象,然后通过getInstance将单例对象返回给外部方法使用。由于静态单例对象没有实例化为IoDHSingleton的成员变量,因此在类加载时不会实例化该对象,以达到延迟加载的效果。当第一次调用getInstance()方法时,会加载内部类HolderClass。这时,HolderClass中的成员变量就会被创建。 实例,而此时的线程安全是由虚拟机保证的。它保证了成员变量只能初始化一次,整个过程不需要同步锁,保证线程安全。但这种方法对编程语言有要求。在java中我们可以这样设计,但对于其他语言则不一定如此。

总结

单例模式是设计模式中最好理解的设计模式,而且这种模式在实际开发中使用得也非常频繁。单例模式的核心是提供一个唯一的实例供整个系统使用。只要我们能够保证系统中获取到的实例是同一个实例,我们就可以称之为单例模式。

本文示例代码地址:https://Gitwww.sychzs.cn/zengchao_workspace/design-pattern

相关文章

最新资讯