设计模式——单例模式

单例模式简介

  单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

  许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

  单例模式的特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

一、懒汉式单例

 1 //懒汉式单例类.在第一次调用的时候实例化自己 
 2 public class Singleton {
 3     private Singleton() {}
 4     private static Singleton single=null;
 5     //静态工厂方法 
 6     public static Singleton getInstance() {
 7          if (single == null) {  
 8              single = new Singleton();
 9          }  
10         return single;
11     }
12 }

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance()这个方法改造,保证了懒汉式单例的线程安全

1.1 在getInstance()方法上加同步

1 public static synchronized Singleton getInstance() {
2          if (single == null) {  
3              single = new Singleton();
4          }  
5         return single;
6 }

缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

1.2 双重检查锁定

 1 public static Singleton getInstance() {
 2     if (singleton == null) {  
 3         synchronized (Singleton.class) {  
 4             if (singleton == null) {  
 5                 singleton = new Singleton(); 
 6             }  
 7         }  
 8     }  
 9     return singleton; 
10 }

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高。

1.3 静态内部类

1 public class Singleton {  
2     private static class LazyHolder {  
3        private static final Singleton INSTANCE = new Singleton();  
4     }  
5     private Singleton (){}  
6     public static final Singleton getInstance() {  
7        return LazyHolder.INSTANCE;  
8     }  
9 }  

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

二、饿汉式单例

2.1 静态常量

1 //饿汉式单例类.在类初始化时,已经自行实例化 
2 public class Singleton1 {
3     private Singleton1() {}
4     private static final Singleton1 single = new Singleton1();
5     //静态工厂方法 
6     public static Singleton1 getInstance() {
7         return single;
8     }
9 }

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2.2 静态代码块

 1 public class Singleton {
 2     private static Singleton instance;
 3     //静态代码块    
 4     static {
 5         instance = new Singleton();
 6     }
 7     private Singleton() {}
 8     public Singleton getInstance() {
 9         return instance;
10     }
11 }

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

三、饿汉式与懒汉式区别

饿汉:饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

懒汉:懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

3.1 线程安全

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

3.2 资源加载和性能

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

四、其他实现方式

4.1 枚举

1 public enum Singleton {
2     INSTANCE;
3     public void whateverMethod() {
4     }
5 }

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。

五、适用场合

  • 需要频繁的进行创建和销毁的对象;
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
  • 工具类对象;
  • 频繁访问数据库或文件的对象。

参考链接:https://blog.csdn.net/qq564425/article/details/81222973

原文地址:https://www.cnblogs.com/john1015/p/13747489.html