`
jaesonchen
  • 浏览: 310025 次
  • 来自: ...
社区版块
存档分类
最新评论

单例这种设计模式

 
阅读更多

随着我们编写代码的深入,我们或多或少都会接触到设计模式,其中单例(Singleton)模式应该是我们耳熟能详的一种模式。本文将比较特别的介绍一下Java设计模式中的单例模式。

概念

单例模式,又称单件模式或者单子模式,指的是一个类只有一个实例,并且提供一个全局访问点。

实现思路

  • 在单例的类中设置一个private静态变量sInstance,sInstance类型为当前类,用来持有单例唯一的实例。
  • 将(无参数)构造器设置为private,避免外部使用new构造多个实例。
  • 提供一个public的静态方法,如getInstance,用来返回该类的唯一实例sInstance。

其中上面的单例的实例可以有以下几种创建形式,每一种实现都需要保证实例的唯一性。

饿汉式

饿汉式指的是单例的实例在类装载时进行创建。如果单例类的构造方法中没有包含过多的操作处理,饿汉式其实是可以接受的。

饿汉式的常见代码如下,当SingleInstance类加载时会执行private static SingleInstance sInstance = new SingleInstance();初始化了唯一的实例,然后getInstance()直接返回sInstance即可。

1
2
3
4
5
6
7
8
9
10
public class SingleInstance {
  private static SingleInstance sInstance = new SingleInstance();
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      return sInstance;
  }
}

饿汉式的问题

  • 如果构造方法中存在过多的处理,会导致加载这个类时比较慢,可能引起性能问题。
  • 如果使用饿汉式的话,只进行了类的装载,并没有实质的调用,会造成资源的浪费。

懒汉式

懒汉式指的是单例实例在第一次使用时进行创建。这种情况下避免了上面饿汉式可能遇到的问题。

但是考虑到多线程的并发操作,我们不能简简单单得像下面代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}

上述的代码在多个线程密集调用getInstance时,存在创建多个实例的可能。比如线程A进入null == sInstance这段代码块,而在A线程未创建完成实例时,如果线程B也进入了该代码块,必然会造成两个实例的产生。

synchronized修饰方法

使用synchrnozed修饰getInstance方法可能是最简单的一个保证多线程保证单例唯一性的方法。
synchronized修饰的方法后,当某个线程进入调用这个方法,该线程只有当其他线程离开当前方法后才会进入该方法。所以可以保证getInstance在任何时候只有一个线程进入。

1
2
3
4
5
6
7
8
9
10
11
12
public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }
  public static synchronized SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}

但是使用synchronized修饰getInstance方法后必然会导致性能下降,而且getInstance是一个被频繁调用的方法。虽然这种方法能解决问题,但是不推荐。

双重检查加锁

使用双重检查加锁,首先进入该方法时进行null == sInstance检查,如果第一次检查通过,即没有实例创建,则进入synchronized控制的同步块,并再次检查实例是否创建,如果仍未创建,则创建该实例。

双重检查加锁保证了多线程下只创建一个实例,并且加锁代码块只在实例创建的之前进行同步。如果实例已经创建后,进入该方法,则不会执行到同步块的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingleInstance {
  private static volatile SingleInstance sInstance;
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      if (null == sInstance) {
          synchronized (SingleInstance.class) {
              if (null == sInstance) {
                  sInstance = new SingleInstance();
              }
          }
      }
      return sInstance;
  }
}

volatile是什么

Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。使用volatile修饰sInstance变量之后,可以确保多个线程之间正确处理sInstance变量。
关于volatile,可以访问深入分析Volatile的实现原理了解更多。

利用static机制

在Java中,类的静态初始化会在类被加载时触发,我们利用这个原理,可以实现利用这一特性,结合内部类,可以实现如下的代码,进行懒汉式创建实例。

1
2
3
4
5
6
7
8
9
10
11
12
public class SingleInstance {
  private SingleInstance() {
  }
  public static SingleInstance getInstance() {
      return SingleInstanceHolder.sInstance;
  }
  private static class SingleInstanceHolder {
      private static SingleInstance sInstance = new SingleInstance();
  }
}

关于这种机制,可以具体了解双重检查锁定与延迟初始化

好奇问题

真的只有一个对象么

其实,单例模式并不能保证实例的唯一性,只要我们想办法的话,还是可以打破这种唯一性的。以下几种方法都能实现。

  • 使用反射,虽然构造器为非公开,但是在反射面前就不起作用了。
  • 如果单例的类实现了cloneable,那么还是可以拷贝出多个实例的。
  • Java中的对象序列化也有可能导致创建多个实例。避免使用readObject方法。
  • 使用多个类加载器加载单例类,也会导致创建多个实例并存的问题。

单例可以继承么

单例类能否被继承需要分情况而定。

可以继承的情况

当子类是父类单例类的内部类时,继承是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BaseSingleton {
  private static volatile BaseSingleton sInstance;
  private BaseSingleton() {
  }
  public static BaseSingleton getInstance() {
      if (null == sInstance) {
          synchronized(BaseSingleton.class) {
              if (null == sInstance) {
                  sInstance = new BaseSingleton();
              }
          }
      }
      return sInstance;
  }
  public static class  MySingleton extends BaseSingleton {
  }
}

但是上面仅仅是编译和执行上允许的,但是继承单例没有实际的意义,反而会变得更加事倍功半,其代价要大于新写一个单例类。感兴趣的童鞋可以尝试折腾一下。

不可以继承的情况

如果子类为单独的类,非单例类的内部类的话,那么在编译时就会出错Implicit super constructor BaseSingleton() is not visible for default constructor. Must define an explicit constructor,主要原因是单例类的构造器是private,解决方法是讲构造器设置为可见,但是这样做就无法保证单例的唯一性。所以这种方式不可以继承。

总的来说,单例类不要继承。

单例 vs static变量

全局静态变量也可以实现单例的效果,但是使用全局变量无法保证只创建一个实例,而且使用全局变量的形式,需要团队的约束,执行起来可能会出现问题。

关于GC

因为单例类中又一个静态的变量持有单例的实例,所以相比普通的对象,单例的对象更不容易被GC回收掉。单例对象的回收应该发生在其类加载器被GC回收掉之后,一般不容易出现。

分享到:
评论

相关推荐

    设计模式——单例模式

    **设计模式——单例模式** 在软件工程中,设计模式是一种在特定场景下解决常见问题的标准方案,可以被复用并提升代码质量。单例模式是设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式...

    单例设计模式的优缺点和设计思想

    单例设计模式是一种在软件工程中广泛使用的创建型设计模式,其核心思想是确保一个类仅有一个实例,并提供一个全局访问点。这种模式在多种场景下具有显著的优势,同时也存在一定的局限性和潜在的问题。 ### 单例设计...

    Java的单例设计模式

    单例设计模式是软件设计模式中的经典模式之一,它的核心思想是确保一个类只有一个实例,并提供全局访问点。这种模式在Java中广泛应用于系统配置、线程池、缓存管理等场景,因为这些场景通常只需要一个共享的全局对象...

    MVC(单例模式)设计模式

    MVC(Model-View-Controller)设计模式是一种软件设计模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式最初在Smalltalk环境中提出,后来在Java等其他编程语言中...

    java单例设计模式的好处

    单例设计模式是软件开发中一种重要的设计模式,它的核心思想是确保一个类只有一个实例,并提供全局访问点。在Java中,单例模式通常用于控制特定类的实例化过程,以达到节省系统资源、控制并发访问和实现数据共享等...

    设计模式之单例模式.pptx

    单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。 比如在某个服务器程序中,该服务器的配置信息存放...

    单例设计模式源码和案例解析

    单例设计模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在Java编程中,单例模式的应用广泛,例如控制资源的共享、管理配置信息等。本篇将深入解析单例设计模式的四种实现方式,并通过...

    C++单例设计模式

    单例设计模式是一种重要的软件设计模式,用于确保一个类只有一个实例,并提供全局访问点。这种模式在C++中尤其常见,因为它允许多个组件共享同一对象,从而提高效率和一致性。以下是对C++单例模式的详细说明: 1. *...

    单例设计模式_单例设计模式_

    单例设计模式是一种在软件工程中广泛使用的设计模式,它的主要目标是确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,且对象需要跨模块共享时特别有用,例如数据库连接、...

    设计模式单例模式

    单例模式是软件设计模式中的一种基础且广泛应用的模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,且对象创建成本较高,或者需要共享资源的情况下非常...

    设计模式之单例模式Java实现和类设计图

    本次我们将深入探讨两种设计模式——单例模式和装饰模式,它们在Java编程中都有着广泛的应用。 首先,让我们来理解“单例模式”。单例模式是一种创建型设计模式,其核心思想是保证一个类只有一个实例,并提供一个...

    设计模式:单例设计模式(全部实现方式)工厂设计模式,抽象工厂模式

    单例设计模式、工厂设计模式和抽象工厂模式是常见的软件开发设计模式。这些设计模式提供了一些有用的思想和实现方式,可以帮助开发人员在设计和实现复杂的软件系统时,更加灵活和高效地进行编程。 单例设计模式是一...

    单例和策略模式示例

    今天我们将深入探讨两种常用的设计模式:单例模式和策略模式。这两种模式在实际开发中有着广泛的应用,它们分别解决了不同的问题,提高了代码的可维护性和可扩展性。 首先,我们来看单例模式。单例模式是一种保证一...

    23钟设计模式之单例模式

    单例模式是一种常用的设计模式,它的核心思想是在整个应用程序中,一个类只能有一个实例存在。单例模式常用于控制资源的共享,例如数据库连接池、日志服务等。单例模式有多种实现方式,常见的包括懒汉式、饿汉式以及...

    大话设计模式--Singleton(单例模式)

    单例模式是软件设计模式中的一种经典模式,它在许多场景下被广泛使用,尤其是在需要全局唯一实例的情况下。本文将深入探讨单例模式的概念、作用、实现方式以及其在实际编程中的应用。 单例模式的核心思想是确保一个...

    设计模式 中的 单例模式和观察者模式

    在给定的标题和描述中,我们关注的是两种重要的设计模式:单例模式和观察者模式。 首先,让我们深入了解单例模式。单例模式是一种确保一个类只有一个实例,并提供全局访问点的设计模式。这种模式在资源管理、缓存、...

    设计模式C++学习之单例模式(Singleton)

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C++中,实现单例模式有多种方法,我们将会深入探讨这一模式的原理、优缺点以及如何在实际编程中应用。 单例模式的核心在于...

    43丨单例模式(下):如何设计实现一个集群环境下的分布式单例模式?1

    单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。在单例模式中,类的构造函数是私有的,防止外部直接创建对象,而是通过静态方法获取该类的唯一实例。单例模式的唯一性通常是在进程范围内,...

    单例设计模式五种例子

    单例设计模式是一种在软件设计中广泛使用的设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象,或者对象的创建代价高昂时特别有用,例如数据库连接、线程池等。在C#中...

    三种工厂设计模式和两种单例模式

    在本文中,我们将深入探讨三种工厂设计模式——简单工厂模式、抽象工厂模式和工厂方法模式,以及两种单例模式——饿汉单例模式和懒汉单例模式。这些模式都是面向对象设计中的重要组成部分,对于理解和构建可维护、可...

Global site tag (gtag.js) - Google Analytics