JAVA编程思想 单例模式

Java设计模式—单例模式

作者:张素干

概念

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,比较常见的有:懒汉式单例、饿汉式单例。
单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
单例模式可以确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

想必有很多人和我一样,最早接触到的设计模式就是单例模式。和其他设计模式相比较,单例模式确实显得简单一些,而且它也是Android开发当中最常用的设计模式之一,从许多开源框架源码中都能看见单例的应用。废话不多说,直接上代码。

一、首先看看单例模式在几个著名框架内的使用情况:

1、EventBus

/** Convenience singleton for apps using a process-wide EventBus instance. */
   public static EventBus getDefault() {
       if (defaultInstance == null) {
           synchronized (EventBus.class) {
               if (defaultInstance == null) {
                   defaultInstance = new EventBus();
               }
           }
       }
       return defaultInstance;
   }

2、Android-Universal-Image-Loader

/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}

从上面的代码中可以看到,大神们造轮子比较青睐的写法还是双重校验锁。尽管受到大神们青睐,但这并不意味着这种写法是完全没有问题的。

二、接下来我们分析一下单例模式的各种写法:

懒汉式

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
   private Singleton() {}  
   private static Singleton single=null;  
   //静态工厂方法   
   public static Singleton getInstance() {  
        if (single == null) {    
            single = new Singleton();  
        }    
       return single;  
   }  
}

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

1、给方法添加同步synchronized

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

同步方法不可避免地会对性能造成一定的影响,因此使用时要慎重考虑。

2、双重校验锁

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

尽管双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
想了解具体原因请参考 Java单例模式中双重检查锁的问题

3、静态内部类

public class Singleton {    
   private static class LazyHolder {    
      private static final Singleton INSTANCE = new Singleton();    
   }    
   private Singleton (){}    
   public static final Singleton getInstance() {    
      return LazyHolder.INSTANCE;    
   }    
}

第三种方法虽然看起来不怎么优雅(个人看法,现在连代码都要看颜值了~~哈哈),但是与 1、2 方法相比较,它确实很好地兼顾到性能和安全两个方面。

饿汉式

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

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用(static关键字的功劳),并且巧妙地利用final关键字保证以后不再改变,所以天生是线程安全的。

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:

1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

参考链接

JAVA设计模式之单例模式

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5a1592b451882575cb73c1d8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞