zoukankan      html  css  js  c++  java
  • 设计模式——单例模式

    单例模式简介

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

      许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个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

  • 相关阅读:
    poj 1013 Counterfeit Dollar
    poj百练2973:Skew数 进制问题
    poj百练2972 进制问题
    poj2080 Calendar
    POJ 1928 The Peanuts
    EXCEL打开CSV文件乱码的解决方法
    希望博客园做个软件职业生存状态调查问卷
    Linq使用Group By [转]
    ADO.NET 从DataTable中获取某列含有的不同值的几种方式
    本该遭拒的十大科技专利:苹果滑动解锁上榜[转]
  • 原文地址:https://www.cnblogs.com/john1015/p/13747489.html
Copyright © 2011-2022 走看看