单例模式的学习

一  单例模式的使用场景

单例模式用于确保某一个类有且只有一个实例,避免产生多个对象消耗过多的资源。所以当你所要定义的类会消耗较多的资源,例如访问IO和数据库等资源,就要考虑使用单例模式了。举个例子,在Android开发中出色的图片加载框架ImageLoader就使用了单例模式。因为ImageLoader中含有线程池、缓存系统、网络请求等等,比较耗费资源,如果没加载一张图片就去新建一个ImageLoader的实例则非常耗费资源,所以程序的设计者使用了单例模式,以让一个应用程序中有且仅有一个ImageLoader的实例。

二  单例模式的实现方法

实现单例模式主要有以下几个重点:

(1)构造方法不对外开放,一般为private

(2)通过静态方法返回实例

(3)确保类的实例有且仅有一个,特别是在多线程的环境下

(4)尽量确保线程安全

掌握内在思想后,就要开始实践了,单例模式的实现方法多种多样,且各有千秋,下面由简至繁一一举例,并且进行点评。

1、饿汉模式

public class Singleton {

     private Singleton() {}

                           private static final Singleton single = new Singleton();//静态工厂方法

public static Singleton getInstance() {

                               return single;}

                               }

上述这种实现方法叫饿汉模式,从代码中可以看到,外届只能通过Singleton.getInstance()方法获得Singleton的实例(single),而这个实例是静态对象,并且在声明的时候就初始化了,这就保证了Sigleton对象的唯一性,以后不再改变,所以天生是线程安全的。

2、懒汉模式

//懒汉式单例类.在第一次调用的时候实例化自己public class Singleton {

                           private Singleton() {}

                           private static  Singleton single=null;//静态工厂方法

public static synchronized Singleton getInstance() {

                               if (single == null) {

                                   single = new Singleton();}

                               return single;}

                               }

懒汉模式同样将构造函数私有化,对外提供Singleton.getInstance()方法获得Singleton的实例(single),不同的是饿汉模式中在类加载的时候就已经初始化了single实例,而懒汉模式中只有开发者调用了Singleton.getInstance()才去创建Single的实例对象(single),同时,细心地读者应该发觉Singleton.getInstance()方法加了一个synchronized 的关键字,这个关键字就是用来保证线程安全的。这种方法实现了单例模式也保证了线程安全,但是每次调用Singleton.getInstance()方法都需要进行同步以保证线程安全造成了很多不必要的同步开销,消耗资源。这个就是懒汉模式的缺点。

3、双重加锁(Double Check Lock)(比较推荐)

public class Singleton {

                           private Singleton() {}

                           private  volatile static  Singleton single=null;//静态工厂方法

public static Singleton getInstance() {

                               if (single == null) {

                                   synchronized (Singleton.class) {

                                       if (single== null) {

                                           single = new Singleton();}

                                   }

                               }

                               return single;}

                               }

Double Check Lock双重加锁,简称DCL,这种方式实现单例既能够在需要使用时才初始化单例,进行了同步保证了线程安全,也提高了资源的利用率。它的亮点在于Singleton.getInstance()方法中的对single进行了两层判空,第一层判空是为了避免不必要的同步,第二层判空是为了在single为null的情况下才创建实例。读者应该注意到单例对象single 定义添加了关键字volatile ,添加该字段是因为程序执行

single = new Singleton();

这个语句时其实做了三步工作,(1)分别是给Singleton的实例分配内存,(2)调用构造函数初始化成员字段,(3)将single对象指向分配的内存空间(single不再为null),由于Java虚拟机是乱序执行的,所以执行顺序可能是(1)(2)(3),也有可能是(1)(3)(2),在多线程情况下,假如(3)执行完成,(2)未执行,线程从A切换线程B,因为此时single不为null,所以线程B直接取走了未执行步骤(2)的single,因为此时single未调用构造函数初始化,所以B线程在使用single时会出错,此时双重加锁就会失效,上面添加的volatile关键字就是为了解决这个问题,它可以保证single每次都从主存中读取。但是volatile也会影响性能,一般来说在Android开发中,高并发的情况比较少见,所以在实际开发中大多数开发者选择把volatile 字段去掉。

4、静态内部类方式(也不错的)

public class Singleton {

                           private static class Holder {

                               private static final Singleton INSTANCE = new Singleton();}

                           private Singleton (){}

                           public static final Singleton getInstance() {

                               return Holder.INSTANCE;}

                               }

DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是在也会存在失效的可能,严重会导致程序崩溃。在《Java并发编程实践》中也谈到了这个问题,并直指这种优化是丑陋的不赞成使用,而提倡了上面这种静态内部类的实现方式。这种方式比上面1、2、3都好一些,既实现了线程安全,又避免了同步带来的性能影响,保证了单例对象的唯一性,同时延迟了单例的实例化。

仁者见仁智者见智,在Android开发实践中,通过阅读别人的代码发现,大部分开发者偏向于使用第三种双重加锁的实现方式,同时第四种方法得到圣书《Java并发编程实践》的力推,优点也是显而易见,所以个人推荐第三第四种实现方式。

你可能感兴趣的:(单例模式的学习)