浅谈单例设计模式探索

        最近在和朋友聊技术时,讨论到了设计模式,发现简单的单例设计模式并不简单,多线程是我们在JAVA开发中常见的场景,在多线程环境下接触的单例模式可能并不单例,下面来详细学习一下单例模式。


一、什么是单例模式

       单例模式(Singleton Pattern):确保某一个类只有一个实例而且自行实例化并向整个系统提供这个实例这个类称为单例类它提供全局访问的方法。单例模式是一种对象创建型模式。

二、单例模式的应用场景

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

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

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

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

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

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

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

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

9. HttpApplication也是单位例的典型应用。熟悉ASP.NET(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式所有的HttpModule都共享一个HttpApplication实例.


总结以上不难看出

单例模式应用的场景一般发现在以下条件下

(1)资源共享的情况下避免由于资源操作时导致的性能或损耗等。如上述中的日志文件应用配置。

(2)控制资源的情况下方便资源之间的互相通信。如线程池等。


三、懒汉及饿汉单例模式

1.单例模式的饿汉式

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

优点从它的实现中我们可以看到这种方式的实现比较简单在类加载的时候就完成了实例化避免了线程的同步问题。

缺点由于在类加载的时候就实例化了所以没有达到Lazy Loading(懒加载)的效果也就是说可能我没有用到这个实例但是它

也会加载会造成内存的浪费(但是这个浪费可以忽略所以这种方式也是推荐使用的)


2.单例模式的懒汉式(线程不安全)

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


三、多线程环境下的单例模式探索

由于饿汉式单例模式会造成部分资源的浪费,因此看起来使用懒汉式单例模式看起来更为合理

但当你指出上面的懒汉式单例模式这段代码在超过一个线程并行被调用的时候会创建多个实例的问题时他很可能会把整个getInstance()方法设为同步(synchronized),代码如下:


public static LazySingleton getInstance()
{
if (instance == null)
{
    synchronized  (LazySingleton.class)
{
instance = new LazySingleton();
            }
        }
return instance;
}
效率较低,多线程环境下并不一定能保证单例

问题貌似得以解决事实并非如此。如果使用以上代码来实现单例还是会存在单例对象不唯一。原因如下

      假如在某一瞬间线程A和线程B都在调用getInstance()方法此时instance对象为null均能通过instance == null的判断。由于实现了synchronized加锁机制线程A进入synchronized锁定的代码中执行实例创建代码线程B处于排队等待状态必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时线程B并不知道实例已经创建将继续创建新的实例导致产生多个单例对象违背单例模式的设计思想因此需要进行进一步改进synchronized中再进行一次(instance == null)判断这种方式称为双重检查锁定(Double-Check Locking)

public class Singleton {  
    /** 
     * 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全 
     */  
    private static volatile Singleton instance=null;  
      
    private Singleton() {};  
      
    public static Singleton getInstance(){  
         if (instance == null) {    
              synchronized (Singleton.class) {    
                  if (instance == null) {    
                      instance = new Singleton();    
                  }    
              }    
          }    
          return instance;    
    }  
}  

需要注意的是如果使用双重检查锁定来实现懒汉式单例类需要在静态成员变量instance之前增加修饰符volatilevolatile修饰的成员变量可以确保多个线程都能够正确处理且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化可能会导致系统运行效率降低因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。


四、内部类及枚举类

1.内部类

public class Singleton{  
  
      
    private Singleton() {};  
      
    private static class SingletonHolder{  
        private static Singleton instance=new Singleton();  
    }   
      
    public static Singleton getInstance(){  
        return SingletonHolder.instance;  
    }  

这种方式跟饿汉式方式采用的机制类似但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同

的地方在饿汉式方式是只要Singleton类被装载就会实例化没有Lazy-Loading的作用而静态内部类方式在Singleton类被装载时

并不会立即实例化而是在需要实例化时调用getInstance方法才会装载SingletonHolder从而完成Singleton的实例化。

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

无法进入的。

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


2.枚举

public enum SingletonEnum {  
      
     instance;   
       
     private SingletonEnum() {}  
       
     public void method(){  
     }  
}  

调用方式:


SingletonEnum.instance.method();  


可以看到枚举的书写非常简单访问也很简单在这里SingletonEnum.instance这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加所以在实际项目开发中很少见人这么写过这种方式也是最好的一种方式如果在开发中JDK满足要求的情况下建议使用这种方式。



五、饿汉式和懒汉式单例模式比较

     饿汉式单例类在类被加载时就将自己实例化它的优点在于无须考虑多线程访问问题可以确保实例的唯一性从调用速度和反应时间角度来讲由于单例对象一开始就得以创建因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象由于在类加载时该对象就需要创建因此从资源利用效率角度来讲饿汉式单例不及懒汉式单例而且在系统加载时由于需要创建饿汉式单例对象加载时间可能会比较长。

      懒汉式单例类在第一次使用时创建无须一直占用系统资源实现了延迟加载但是必须处理好多个线程同时访问的问题特别是当单例类作为资源控制器在实例化时必然涉及资源初始化而资源初始化很有可能耗费大量时间这意味着出现多线程同时首次引用此类的机率变得较大需要通过双重检查锁定等机制进行控制这将导致系统性能受到一定影响。


附上学习时手写demo:

/**
 * 单例设计模式的衍生
 */

class Singleton {

    private volatile static Singleton _instance;

    private Singleton() {
        // preventing Singleton object instantiation from outside
    }

    /**
     * version 1.0
     * 懒汉式单例模式
     * 多线程访问时可能会创建多个实例
     * 违反单例原则
     * 适合单线程使用
     */

    public static Singleton getInstance() {
        if (_instance == null) {
            _instance = new Singleton();
        }
        return _instance;
    }

    /**
     * version 2.0
     * 多线程访问时仍然只会创建一个实例
     * 是线程安全的
     * 但是犹豫加入了synchronized同步关键字 控制并发锁
     * 造成了性能的不必要开销和浪费
     * 不是最佳模式
     */

    public static synchronized Singleton getInstanceTS() {
        if (_instance == null) {
            _instance = new Singleton();
        }
        return _instance;
    }

    /**
     * version 3.0
     * 双重检查锁定单例模式
     * 代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建
     * 多线程环境下使用
     * 目的是最小化同步的成本和提高性能
     * 只锁定关键部分的代码
     * 必须使用volatile 修饰实体类
     */

    public static Singleton getInstanceDC() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null) {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }


    /**
     * version 4.0
     * 静态单例对象没有作为Singleton的成员变量直接实例化,
     * 因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,
     * 在该内部类中定义了一个static类型的变量instance,
     * 此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。
     * 由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。
     */

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

    public static Singleton getInstanceMS()
    {
        return HolderClass.instance;
    }


}





参考文章:

设计模式之——单例模式(Singleton)的常见应用场景

Java设计模式—单例设计模式(Singleton Pattern)完全解析

如何在Java中使用双重检查锁实现单例
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值