• 单例模式 (Singleton pattern)


    What is Singleton pattern?

    In Wikipedia, there is an explanation:"In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object."

    一、什么是单例模式?

    在维基百科中,是这样解释的,“在软件工程中,单例模式指的是对类加以限制,只允许创建一个对象的设计模式”。

    也就是说,在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个(当然也可以不存在)。

    单例模式是一种对象创建型设计模式,在《设计模式:可复用面向对象软件的基础》一书中提到,单例模式:“保证一个类仅有一个实例,并提供一个访问它的全局访问点”。

    让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建(通过截取创建新对象的请求)。

    二、我们为什么要使用单例模式?

    单例模式应该是设计模式中最简单的一种设计模式,它的应用场景如下:

    在工作过程中,有些对象我们只需要一个,比如线程池、缓存、硬件设备等,

    如果有多个实例同时使用,可能会造成执行冲突、结果不一致等问题。

    比如我们创建了多个打印程序实例,或打印机对象,但实际的打印设备只有一台,或者打印假脱机只有一个,程序执行时,就可能会造成打印结果的混乱并使程序失去可再现性。

    再比如,在一个父容器中点击某个菜单项打开一个子窗口,如果不加以控制的话,每次单击菜单项都会打开一个新窗口。这不仅会浪费内存资源,在程序逻辑上也是不可以接受的。

    那么我们该如何解决这个问题呢?这就要用到下面所要详细介绍的单例模式。

    三、经典单例模式

    1.经典单例模式的UML图如下:

    2.代码实现如下:

     1 public class Singleton {
     2 
     3     // 静态的instance对象,保证全局唯一性
     4     private static Singleton instance = null;
     5 
     6     // 私有的构造函数,防止外部用new关键字创建实例对象
     7     private Singleton() {
     8 
     9     }
    10 
    11     // 对外的公共静态实例方法,从类级别直接可以调用此方法
    12     public static Singleton getInstance() {
    13 
    14         // 通过判断instance是否为null,决定是否创建对象
    15         if (instance == null) {
    16             instance = new Singleton();
    17         }
    18         return instance;
    19     }
    20 }

     注意:通过Java反射机制是能够实例化构造方法为private的类的,此时基本上所有的Java单例实现失效。

    (事实上我们一般不需要这样做,所以单例模式仍有其存在的意义)

    四、单例模式在多线程环境下存在的问题

    对于上述代码,我们可以考虑这样一种情况:

    当有两个Singleton类的实例同时被创建,并运行于不同的线程中。假如A线程在执行完上述代码第15行后意外阻塞,而B线程将继续运行,执行第16行后创建instance实例,此后如果A线程重新回到就绪状态,并得到处理器资源,进入运行状态,将再次创建instance对象,此时单例模式失效。为了解决这一问题,程序员们对单例模式进行了优化。

    五、单例模式的分类

    1.饿汉式单例

    急切创建实例,在类初始化时,便自行实例化。

     1 public class Singleton1 {
     2 
     3     //已经实例化
     4     private static final Singleton1 single = new Singleton1();
     5 
     6     //私有的默认构造方法
     7     private Singleton1() {
     8     
     9     }
    10     
    11     //静态工厂方法
    12     public static Singleton1 getInstance() {
    13         return single;
    14     }
    15 }

    2.懒汉式单例

    在第一次调用的时候再实例化。

     1 public class Singleton2 {
     2 
     3     //注意,这里没有final
     4     private static Singleton2 single = null;
     5     
     6     //私有的默认构造子
     7     private Singleton2() {
     8     
     9     }
    10     
    11     //静态工厂方法,用synchronized加锁
    12     public synchronized static Singleton2 getInstance() {
    13          if (single == null) {
    14              single = new Singleton2();
    15          }
    16         return single;
    17     }
    18 }

    这种方式也存在一定的问题,同样考虑特殊情况,当A线程执行到getInstance内时意外出现阻塞,此时B线程中的实例也不能够执行getInstance操作,出现阻塞。

    为了解决这一问题,对此方法可以做进一步优化:双重检查加锁法。

    虽然此方法同样不完美,但相对于上面一种情形,已经做到了一定的优化。

     1 public class Singleton3 {
     2     
     3     // 添加关键词volatile
     4     private volatile static Singleton3 single = null;
     5     
     6     //私有的默认构造器
     7     private Singleton3() {
     8         
     9     }
    10     
    11     public static Singleton3 getInstance() {
    12          if (single == null) {
    13              //同步锁
    14             synchronized (Singleton3.class) {
    15                 if (single == null) {
    16                     single = new Singleton3();
    17                 }
    18             }
    19         }
    20         return single;
    21     }
    22 }

    3.登记式单例

    将类名注册,下次从里面直接获取。

     1 import java.util.HashMap;
     2 import java.util.Map;
     3 
     4 public class Singleton4 {
     5     private static Map<String,Singleton4> map = new HashMap<String,Singleton4>();
     6     
     7     static{
     8         Singleton4 single = new Singleton4();
     9         map.put(single.getClass().getName(), single);
    10     }
    11     //保护的默认构造器
    12     protected Singleton4(){
    13     
    14     }
    15     
    16     //静态工厂方法,返还此类惟一的实例
    17     public static Singleton4 getInstance(String name) {
    18         if(name == null) {
    19             name = Singleton3.class.getName();
    20         }
    21         if(map.get(name) == null) {
    22             try {
    23                 map.put(name, (Singleton4) Class.forName(name).newInstance());
    24             } catch (InstantiationException e) {
    25                 e.printStackTrace();
    26             } catch (IllegalAccessException e) {
    27                 e.printStackTrace();
    28             } catch (ClassNotFoundException e) {
    29                 e.printStackTrace();
    30             }
    31         }
    32         return map.get(name);
    33     }
    34 }

    参考资料:

    1.极客学院hexter老师的课程—— 设计模式之单例模式

    2.《设计模式:可复用面向对象软件的基础》

      Erich Gamma,Richard Helm,Ralph Johnson著

    3.博文:蛊惑Into—— Java单例模式详解

    4.维基百科:Singleton Pattern

    推荐阅读:

    博文:赵学智@行胜于言——  设计模式培训之一:为什么要用单例模式? 

    博文:都市耕牛—— 登记式单例实现单例模式的继承(限定一个抽象类的所有子类都必须是单例)

  • 相关阅读:
    SqlServer 本地连接打不开
    Ftp服务器搭建方法
    SQL分页查询
    sql 多表更新
    SQL 循环截取指定位置字符串
    创建在“system.net/defaultProxy”配置节中指定的 Web 代理时出错。
    python入门学习的第一天
    操作系统第二章总结/
    操作系统第一章总结/
    终端执行for循环
  • 原文地址:https://www.cnblogs.com/yanqiang/p/4875684.html
Copyright © 2020-2023  润新知