java 设计模式--单例模式

来源:互联网 发布:ab plc编程软件 编辑:程序博客网 时间:2024/06/07 16:45

一、什么是单例模式(Singleton Pattern)
java中单例模式是一种常见的设计模式,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。单例模式只是实例化一次,之后可以直接访问该唯一的对象。
单例模式的特点:

  1.  单例类只能有一个实例;  2.  单例类必须自己创建自己的唯一实例;  3.  单例 类必须给所有其他对象提供这一实例

单例模式的意图:

    保证只有一个实例,并提供一个访问它的全局访问点

单例模式主要解决的问题:

      主要解决一个全局使用的类频繁的创建和销毁。

单例模式何时使用:

      控制实例数量,节省资源。

单例模式如何解决这个问题:

   判断系统中是否有这个单例,如果有则返回,没有则创建。 

单例模式关键点:

      构造函数的私有性

单例模式的应用场景:

    在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例

二、单例模式几种写法
1.懒汉式单例

 public class Singleton {    //静态实例    private static Singleton instance ;    //私有的构造方法    private Singleton(){}    //静态工厂方法    public static Singleton getInstance(){        if(null == instance){            instance = new Singleton();        }        return instance;    }}
 在不考虑并发的情况下,这种标准的可以保证取到唯一的实例。   首先,静态实例,带有static关键字的属性在每一个类中都是唯一的;                                                           其次,私有的构造方法,可以限制客户端随意创造实例;   再次,公共的静态工厂方法,返回唯一的实例;                      最后,只有在没有的时候才会实例化,有就会返回。

2.线程安全的懒汉式单例

   public class Singleton {    private  static Singleton instance;    private Singleton(){}    public static synchronized Singleton getInstance(){        if (instance == null){            instance  = new Singleton();        }        return instance;    }}
这种方法是对获取实例的方法,进行加锁,也就是将整个实例获取的方法同步,这样当多个线程用时访问这个方法的时候,只有一个线程获取锁,其他线程都会处于挂起等待的状态,好处是可以保证同步访问的时候创造多个实例的现象,但是由于加锁,会是很多同步线程等待,造成了资源的浪费。

3.双重验证的单例(DCL,即 double-checked locking)

public class SingletonSync {    private static  SingletonSync instance;    private SingletonSync(){}    public static synchronized SingletonSync getInstance(){        if (instance == null ){            synchronized (SingletonSync.class){                if (instance == null){                    instance = new SingletonSync();                }            }        }        return instance;    }}
    假设我们去掉同步块中的是否为null的判断,有这样一种情况,假设1线程和2线程都在同步块外面判断了SingletonSync 为null,结果1线程首先获得了线程锁,进入了同步块,然后1线程会创造一个实例,此时SingletonSync 已经被赋予了实例,1线程退出同步块,直接返回了第一个创造的实例,此时2线程获得线程锁,也进入同步块,此时1线程其实已经创造好了实例,2线程正常情况应该直接返回的,但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,所以2线程也会创造一个实例返回,此时就造成创造了多个实例的情况。    经过刚才的分析,貌似上述双重加锁的示例看起来是没有问题了,但如果再进一步深入考虑的话,其实仍然是有问题的。 如果我们深入到JVM中去探索上面这段代码,它就有可能(注意,只是有可能)是有问题的。 因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的错误。 首先要明白在JVM创建新的对象时,主要要经过三步。
1.分配内存2.初始化构造器3.将对象指向分配的内存的地址
    这种顺序在上述双重加锁的方式是没有问题的,因为这种情况下JVM是完成了整个对象的构造才将内存的地址交给了对象。但是如果2和3步骤是相反的(2和3可能是相反的是因为JVM会针对字节码进行调优,而其中的一项调优便是调整指令的执行顺序),就会出现问题了。     因为这时将会先将内存地址赋给对象,针对上述的双重加锁,就是说先将分配好的内存地址指给SingletonSync ,然后再进行初始化构造器,这时候后面的线程去请求getInstance方法时,会认为SingletonSync 对象已经实例化了,直接返回一个引用。如果在初始化构造器之前,这个线程使用了SingletonSync ,就会产生莫名的错误。所以我们在语言级别无法完全避免错误的发生,我们只有将该任务交给JVM,所以有一种比较标准的单例模式。如下所示。

4.登记式(静态内部类)单例

public class Singleton {    private Singleton(){}    public static final Singleton getInstance(){        return SingletonInstance.INSTANCE;    }    private static class SingletonInstance {        static Singleton INSTANCE = new Singleton();    }}
   首先来说一下,这种方式为何会避免了上面莫名的错误,主要是因为一个类的静态属性只会在第一次加载类时初始化,这是JVM帮我们保证的,所以我们无需担心并发访问的问题。所以在初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。

5.饿汉式

public class Singleton {    private static Singleton instance = new Singleton();    private Singleton() {    }    public static Singleton getInstance() {        return instance;    }}
   它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果    

6.枚举式

public enum  Singleton {    INSTANCE;    public void uselessMethod(){    }}
   这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。

总结:

一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 5种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3种双检锁方式。

参考资料:http://www.cnblogs.com/zuoxiaolong/p/pattern2.html
http://www.runoob.com/design-pattern/singleton-pattern.html