单例模式的十种写法,你会几个?

简介: 单例模式是一种最常见的设计模式,写法也比较多,在这篇文章里面主要是对单例模式的各种写法进行一个介绍。面试的时候会对其中两三种进行体会,而且我还遇到了口述单例模式的例子。重要性就不言而喻了吧。

一、单例模式的介绍


单例模式的重要点在于两个,一个是在哪些地方使用到了单例模式,一个是单例模式如何写。之前只考虑到了如何写,但是哪些地方使用到了,表述的不是很清楚。这一次我找了几个实际例子。


概念:单例模式确保某个类只有一个实例。


有一个通俗的理解,那就是在古代,全国就一个皇帝。如何确保一个皇帝?这就是单例模式。


先看如何写,然后再看在哪用。


二、单例模式的各种写法


1、懒汉式:基本写法


懒汉式就是什么时候用,什么时候创建类的实例。

public class Singleton {
   private Singleton() {}//构造方法
   private static Singleton single=null;
   public static Singleton getInstance() {
        if (single == null) {  
            single = new Singleton();
        }  
       return single;
  }
}

特点:


(1)线程不安全(并发时可能出现多个单例)


(2)构造方法为private,限定了外部只能从getInstance去获取单例


(3)使用static关键字,表明全局只有一份节约了资源,但是如果单例对象比较复杂,new时就比较耗时间。这一点要注意。


上面最主要的缺点就是线程不安全,因此想要解决这个问题,只需要对方法加锁即可。


2、懒汉式:使用synchronized 同步


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

特点:


(1)此时线程安全


(2)因为加了锁,此时如果单例对象复杂,不仅耗内存,而且new的时间更长,效率更低。


但是上面这种效率不高还有另外一个原因。一个线程过来想要创建单例,首先要进行synchronized锁判断,接下来判断单例是否为空,如果为空,那就创建单例。


既然上面每个线程过来都需要锁判断和单例是否为空的判断,这样做有点耗时,毕竟锁判断是比较耗时的。


3、懒汉式:双重检查锁定


为了解决上面的这个问题,才有了双重检查锁定。

public class Singleton{
private static Singleton instance;  
   private Singleton (){}
public static Singleton getInstance() {
  //第一次检查
   if (singleton == null) {  
     synchronized (Singleton.class) {
        //第二次检查
        if (singleton == null) {  
           singleton = new Singleton();
        }  
    }  
  }  
  return singleton;
}

特点:


(1)线程安全


(2)耗内存,而且单例对象比较复杂,比较耗时,但是对单重锁效率提升不少。


此时为了减少锁的判断量,只需要对单例进行判断即可,如果不为空直接返回,如果为空,那就创建新的单例。


4、饿汉式:基本写法(instance为private)


恶汉式是一开始就先建好,用的时候直接返回即可。

public class Singleton {
   private Singleton() {}
   //提前创建一个Singleton
   private static final Singleton instance = new Singleton();
   //有调用者直接就拿出来给了
   public static Singleton getInstance() {
       return instance;
  }
}

特点:


(1)线程安全(因为提前创建了,所以是天生的线程安全)


(2)单例对象修饰为private,只能通过getInstance获取。


此时单例因为有static修饰,因此在类加载的时候就会初始化,这对应用的启动会造成一定程度的影响。


5、饿汉式:基本写法(instance为public)


public class Singleton {
   public static final Singleton instance = new Singleton();
   private Singleton() {}
}

和上面没什么区别。


6、饿汉式:静态代码块


public class Singleton {
   private Singleton instance = null;
   private Singleton() {}
// 初始化顺序:基静态、子静态 -> 基实例代码块、基构造 -> 子实例代码块、子构造
   static {
       instance = new Singleton();
  }
   public static Singleton getInstance() {
       return this.instance;
  }
}

特点:


(1)线程安全


(2)类初始化时实例化 instance


7、静态内部类


public class Singleton {  
//静态内部类里面创建了一个Singleton单例
   private static class InstanceHolder {  
      private static final Singleton INSTANCE = new Singleton();  
  }  
   private Singleton (){}  
   public static final Singleton getInstance() {  
      return InstanceHolder.INSTANCE;  
  }  
}

特点:


(1)线程安全


(2)效率高,避免了synchronized带来的性能影响


这种就好很多。很多大佬都推荐。


8、枚举式


public enum Singleton {
   INSTANCE;
   // 枚举同普通类一样,可以有自己的成员变量和方法
   public void getInstance() {
       System.out.println("Do whatever you want");
  }
}

特点:


(1)线程安全(枚举类型默认就是安全的)


(2)避免反序列化破坏单例


枚举类型更好,但是枚举类型会造成更多的内存消耗。枚举会比使用静态变量多消耗两倍的内存,如果是Android应用,尽量避免。原因的话,是因为枚举类型会在编译时转化为一个类,会涉及很多复杂的操作,这里就先不逼逼了。


9、CAS方式


public class Singleton {
  private static final AtomicReference<Singleton> INSTANCE
  = new AtomicReference<Singleton> ();
  private Singleton() {}
  public static Singleton getInstance() {
      for(;;) {
          Singleton instance = INSTANCE.get();
          if(instance != null) {
              return instance;
          }
          instance = new Singleton();
          if(INSTANCE.compareAndSet(null, instance)) {
              return instance;
          }
      }
  }
}

特点:


(1)优点:不需要使用传统的锁机制来保证线程安全,CAS 是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。


如果不理解CAS的话,可以看这篇文章:


java并发编程CAS机制原理分析(面试必问,学习中必会的一个知识点)


(2)缺点:如果忙等待一直执行不成功(一直在死循环中),会对 CPU 造成较大的执行开销。而且,这种写法如果有多个线程同时执行 singleton = new Singleton(); 也会比较耗费堆内存。


10、Lock机制


// 类似双重校验锁写法
public class Singleton {
      private static Singleton instance = null;
      private static Lock lock = new ReentrantLock();
      private Singleton() {}
      public static Singleton getInstance() {
          if(instance == null) {
              lock.lock(); // 显式调用,手动加锁
              if(instance == null) {
                  instance = new Singleton();
              }
              lock.unlock(); // 显式调用,手动解锁
          }
          return instance;
      }
}

当然还有一些其他的实现单例的写法,比如说登记式单例等等。


三、单例模式的使用


1.Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?不信你自己试试看哦~


  1. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。


  1. 网站的计数器,一般也是采用单例模式实现,否则难以同步。


  1. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。


  1. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。


6.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。


  1. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

  2. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。


四、总结


v2-84566685e195172486a9fc94cba2a490_1440w.jpg

两种场景可能导致非单例的情况


场景一:如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例


场景二:如果 Singleton 实现了 java.io.Serializable 接口,那么这个类的实例就可能被序列化和反序列化。


单例的写法基本上就是这些,可能在不同的场景下使用不同的方式,对我来说,在后端更经常使用的就是枚举类型,但是Android开发当中很少使用。

相关文章
|
7月前
|
安全 C++
C++:谈谈单例模式的多种实现形式
C++:谈谈单例模式的多种实现形式
|
7月前
单例模式例子
单例模式例子
|
7月前
|
设计模式 Java 数据库
二十三种设计模式全面解析-单例设计模式:解密全局独一无二的实例创造者
二十三种设计模式全面解析-单例设计模式:解密全局独一无二的实例创造者
|
iOS开发
书写一个严谨的单例
什么是单例? 一个类只有一个实例(一个对象),而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源
书写一个严谨的单例
|
设计模式 安全 Java
java设计模式之单例设计模式的妙用
1.设计模式 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美地解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因
86 1
|
设计模式 SQL 缓存
二十三天搞懂设计模式之单例模式的七种写法
二十三天搞懂设计模式之单例模式的七种写法
|
设计模式
用两个小例子来解释单例模式中的“双重锁定”
学习单例模式时,好多人都不太理解双重锁定。学完后突然想到一个很有趣的例子。
用两个小例子来解释单例模式中的“双重锁定”
|
设计模式 SQL 存储
【面试干货】单例模式的七种写法
【面试干货】单例模式的七种写法
177 0
【面试干货】单例模式的七种写法
|
安全 Java 容器
单例模式的 8 种写法,整理非常全!
单例模式即一个 JVM 内存中只存在一个类的对象实例。
单例模式的 8 种写法,整理非常全!
|
Java 编译器 C++
“生而有值”—教你使用构造函数 | 带你学《Java面向对象编程》之五
本节结合多组实例从多个方面介绍了重写构造函数的意义以及构造函数与setter函数的异同,指出了一些编写构造函数相关的注意事项。
“生而有值”—教你使用构造函数   | 带你学《Java面向对象编程》之五