计划,每天学习两小时,坚持带来大改变。

你真的了解单例模式么?

设计模式 阿尤 1122浏览 0评论
单例模式,很多初学者认为单例模式很简单,并且认为自己已经掌握了这种设计模式。但事实上,你真的了解单例模式了么。

一,单例模式的5中写法。(回字的四种写法,哈哈。)
    1,懒汉式
          (1)线程不安全的懒汉式
Java代码 

  • public class Singleton {   
  •     private static Singleton instance;   
  •     private Singleton (){}   
  •    
  •     public static Singleton getInstance() {   
  •     //线程a↓ 线程b↓   
  •     if (instance == null) {  
  •    //线程a 创建一个对象 ,线程b 在这又会创建一次  
  •         instance = new Singleton();   
  •     }   
  •     return instance;   
  •     }   
  • }    

            初学单例模式常见的写法,懒加载,但是这种写法在实际中应用很少,因为会有线程安全问题。

          (2)线程安全的懒汉式
Java代码  

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

            用加锁的方式避免了线程安全问题,但是每一次都会判断锁,性能较低。

    2,饿汉式
          (1)恶汉 No1
Java代码 

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

            在类初始化的时候就已经创建了单例对象,导致类的初始化 不仅仅 只有调用 getInstance方法。调用静态方法或者静态变量等等。
所以没有 懒加载 的效果。

          (2)恶汉 No2
Java代码 

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

            和恶汉 No1 的效果一样。都是在类的初始化的时候去创建实例

    3,静态内部类
Java代码  

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

    这种方式我很喜欢,既保证了线程安全又实现了懒加载。因为当外部的类初始化的时候,不会导致内部类的初始化。只有当调用getInstance()的时候,才会创建这个单利实例。懒加载的优点:当这个instance是一个非常消耗资源的对象。我们可以让它延迟加载。在合适的时机才会创建对象。

   4,枚举
Java代码 

  • public enum Singleton {  
  •     INSTANCE;  
  •     public void doSomeThing() {  
  •         //do someThing  
  •     }  
  • }  

    这种也是非常推荐的方式,枚举是JDK1.5的特性,所以用的比较少。这种方式代码简单,并且保证的线程安全,还能防止反序列化创建新的对象,但是也同事失去了懒加载的特性。

    5,双重校验锁
Java代码 

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

    这种写法,这种方式像是“线程安全的懒汉模式的增强版”比较麻烦。并且在JDK1.5之前,由于instance= new Singleton();这句话不是原子操作,这句话分为三步
    1.给Singleton的实例分配内存。
    2.初始化Singleton的构造器
    3.将instance对象指向分配的内存空间(注意到这步instance就非null了)。
由于java编译器out-of-order(允许乱序执行)以及JMM中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的。也就是 不一定是 1,2,3的顺序,如过是1,3,2. 在3执行完的时候,instance就非null了。此时切换到第二个线程,直接拿走这个实例使用,就会报错。但是在JDK1.5之后 这种模式才能达到正常的效果。
具体参见:http://blog.163.com/lby67224262@126/blog/static/1714153412011121104932660

二,你写的单例模式真的能用嘛?

下面我们考虑几种会破坏单例的情况,和解决办法
1,多个classloader 获得 不同的单例对象。举个例子,一些servlet容器可能用不同的classloder 去加载servlet,如果servlet引用了同一个单例类就会产生多个实例。
    如果想你的单例类能被同一个类加载器加载,那么就必须自己制定这个类加载器。代码如下
Java代码 

  • public class Snippet {  
  •     private static Class getClass(String classname)  
  •             throws ClassNotFoundException {  
  •         ClassLoader classLoader = Thread.currentThread()  
  •                 .getContextClassLoader();  
  •         if (classLoader == null)  
  •             classLoader = Singleton.class.getClassLoader();  
  •         return (classLoader.loadClass(classname));  
  •     }  
  • }  


2,你的单例也有可能实现序列化接口,只要是实现了次接口,就有可能被反序列化。没一次反序列化创建对象,就会产生一个新的单例类的实例。解决办法如下。
Java代码  

  • public class Singleton implements java.io.Serializable {  
  •     public static Singleton INSTANCE = new Singleton();  
  •     protected Singleton() {  
  •         // Exists only to thwart instantiation.  
  •     }  
  •     private Object readResolve() {  
  •         return INSTANCE;  
  •     }  
  • }  

3,反射,还能访问private的构造方法。这样就可以创建很多对象。解决办法如下。

      反射时可以使用setAccessible方法来突破private的限制,我们需要做到第一点工作的同时,还需要在在ReflectPermission("suppressAccessChecks") 权限下使用安全管理器(SecurityManager)的checkPermission方法来限制这种突破。一般来说,不会真的去做这些事情,都是通过应用服务器进行后台配置实现。具体请参考http://blog.csdn.net/yaerfeng/article/details/7103397

4,涉及到跨JVM(集群、远程EJB等)时 也会出现产生多个单例类的对象的问题。

      在一个多元 JVM 环境中,每个 JVM 拥有自己对于该单例对象的拷贝,这将导致很多问题,尤其是在对于资源的访问需要受限和加锁的集群的环境中。
      解决方式可以通过应用服务器提供的API 或者第三方的工具。详情参考http://blog.csdn.net/defonds/article/details/12705677

转载请注明:阿尤博客 » 你真的了解单例模式么?

游客
发表我的评论 换个身份
取消评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  • 验证码 (必填)点击刷新验证码