美文网首页
Java设计模式—单例模式

Java设计模式—单例模式

作者: 怡红快绿 | 来源:发表于2017-11-22 16:09 被阅读0次

概念

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

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

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

2、双重校验锁

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

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

3、静态内部类(Holder模式)
我们既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。

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

相对于饿汉模式,Holder模式仅增加了一个静态内部类的成本,与饱汉的变种3效果相当(略优),都是比较受欢迎的实现方式。同样建议考虑。

饿汉式

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

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

Android Studio可以直接新建单例模式类,使用的就是饿汉式写法,我们可以看看。

新建单例模式类.png
代码如下:
/**
 * Created by QianWei on 2017/11/23.
 */
public class Singleton1 {
    private static Singleton1 ourInstance = new Singleton1();

    public static Singleton1 getInstance() {
        return ourInstance;
    }

    private Singleton1() {
    }
}

饿汉式和懒汉式区别

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

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

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

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

三、总结

上面的分析都忽略了反射和序列化的问题。通过反射或序列化,我们仍然能够访问到私有构造器,创建新的实例破坏单例模式。此时,只有枚举模式能天然防范这一问题。


参考链接
JAVA设计模式之单例模式
程序猿应该记住的几条基本规则
面试中单例模式有几种写法

相关文章

  • JAVA设计模式 - 单例模式

    JAVA设计模式 - 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一...

  • 单例模式

    JAVA设计模式之单例模式 十种常用的设计模式 概念: java中单例模式是一种常见的设计模式,单例模式的写法...

  • Java中单例模式你用的哪一种?

    一起讨论java中的单例模式。单例模式是java设计模式中算是最简单的设计模式了。 * java实现单例模式的写法...

  • JAVA设计模式之单例模式

    JAVA设计模式之单例模式

  • 设计模式

    Java 设计模式情景分析 ——单例模式 Java 设计模式情景分析——建造者模式 Java 设计模式情景分析——...

  • Java设计模式教程

    Java设计模式教程 Java工厂设计模式 Java抽象工厂模式 Java单例模式 Java建造者(Builder...

  • 设计模式——单例模式

    设计模式——单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,这种类型...

  • 设计模式

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式...

  • 设计模式《一》单例模式

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属...

  • 设计模式之单例模式详解(附源代码)

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属...

网友评论

      本文标题:Java设计模式—单例模式

      本文链接:https://www.haomeiwen.com/subject/ukanvxtx.html