zoukankan      html  css  js  c++  java
  • 单列模式

    cantellow

    转载请注明出处:http://cantellow.iteye.com/blog/838473

    第一种(懒汉,线程不安全):

    Java代码  
    1. public class Singleton {  
    2.     private static Singleton instance;  
    3.     private Singleton (){}  
    4.   
    5.     public static Singleton getInstance() {  
    6.     if (instance == null) {  
    7.         instance = new Singleton();  
    8.     }  
    9.     return instance;  
    10.     }  
    11. }  
    public class Singleton {
        private static Singleton instance;
        private Singleton (){}
    
        public static Singleton getInstance() {
    	if (instance == null) {
    	    instance = new Singleton();
    	}
    	return instance;
        }
    }
     

     这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

    第二种(懒汉,线程安全):

    Java代码  
    1. public class Singleton {  
    2.     private static Singleton instance;  
    3.     private Singleton (){}  
    4.     public static synchronized Singleton getInstance() {  
    5.     if (instance == null) {  
    6.         instance = new Singleton();  
    7.     }  
    8.     return instance;  
    9.     }  
    10. }  
    public class Singleton {
        private static Singleton instance;
        private Singleton (){}
        public static synchronized Singleton getInstance() {
    	if (instance == null) {
    	    instance = new Singleton();
    	}
    	return instance;
        }
    }
     

     这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

    第三种(饿汉):

    Java代码  
    1. public class Singleton {  
    2.     private static Singleton instance = new Singleton();  
    3.     private Singleton (){}  
    4.     public static Singleton getInstance() {  
    5.     return instance;  
    6.     }  
    7. }  
    public class Singleton {
        private static Singleton instance = new Singleton();
        private Singleton (){}
        public static Singleton getInstance() {
    	return instance;
        }
    }
     

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

    第四种(饿汉,变种):

    Java代码  
    1. public class Singleton {  
    2.     private Singleton instance = null;  
    3.     static {  
    4.     instance = new Singleton();  
    5.     }  
    6.     private Singleton (){}  
    7.     public static Singleton getInstance() {  
    8.     return this.instance;  
    9.     }  
    10. }  
    public class Singleton {
        private Singleton instance = null;
        static {
    	instance = new Singleton();
        }
        private Singleton (){}
        public static Singleton getInstance() {
    	return this.instance;
        }
    }
     

     表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

    第五种(静态内部类):

    Java代码  
    1. public class Singleton {  
    2.     private static class SingletonHolder {  
    3.     private static final Singleton INSTANCE = new Singleton();  
    4.     }  
    5.     private Singleton (){}  
    6.     public static final Singleton getInstance() {  
    7.     return SingletonHolder.INSTANCE;  
    8.     }  
    9. }  
    public class Singleton {
        private static class SingletonHolder {
    	private static final Singleton INSTANCE = new Singleton();
        }
        private Singleton (){}
        public static final Singleton getInstance() {
    	return SingletonHolder.INSTANCE;
        }
    }
     

    这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

    第六种(枚举):

    Java代码  
    1. public enum Singleton {  
    2.     INSTANCE;  
    3.     public void whateverMethod() {  
    4.     }  
    5. }  
    public enum Singleton {
        INSTANCE;
        public void whateverMethod() {
        }
    }

     这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

    第七种(双重校验锁):

    Java代码  
    1. public class Singleton {  
    2.     private volatile static Singleton singleton;  
    3.     private Singleton (){}  
    4.     public static Singleton getSingleton() {  
    5.     if (singleton == null) {  
    6.         synchronized (Singleton.class) {  
    7.         if (singleton == null) {  
    8.             singleton = new Singleton();  
    9.         }  
    10.         }  
    11.     }  
    12.     return singleton;  
    13.     }  
    14. }  
    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;
        }
    }
     

     这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

    在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

    总结

    有两个问题需要注意:

    1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

    2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

    对第一个问题修复的办法是:

    Java代码  
    1. private static Class getClass(String classname)      
    2.                                          throws ClassNotFoundException {     
    3.       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
    4.       
    5.       if(classLoader == null)     
    6.          classLoader = Singleton.class.getClassLoader();     
    7.       
    8.       return (classLoader.loadClass(classname));     
    9.    }     
    10. }  
    private static Class getClass(String classname)    
                                             throws ClassNotFoundException {   
          ClassLoader classLoader = Thread.currentThread().getContextClassLoader();   
        
          if(classLoader == null)   
             classLoader = Singleton.class.getClassLoader();   
        
          return (classLoader.loadClass(classname));   
       }   
    }

     对第二个问题修复的办法是:

    Java代码  
    1. public class Singleton implements java.io.Serializable {     
    2.    public static Singleton INSTANCE = new Singleton();     
    3.       
    4.    protected Singleton() {     
    5.         
    6.    }     
    7.    private Object readResolve() {     
    8.             return INSTANCE;     
    9.       }    
    10. }   
    public class Singleton implements java.io.Serializable {   
       public static Singleton INSTANCE = new Singleton();   
        
       protected Singleton() {   
          
       }   
       private Object readResolve() {   
                return INSTANCE;   
          }  
    } 
     

    对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。

    ========================================================================

     总结的很到位:

    不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。

    我很高兴有这样的读者,一起共勉。

     
    参考知识库
    语音识别与合成知识库 574  关注 | 316  收录
    计算机视觉知识库 1339  关注 | 252  收录
    自然语言理解和处理知识库 491  关注 | 87  收录
    知识工程知识库 157  关注 | 74  收录
    评论
                
    第三种稍微有点Java基础就知道是错的,被瞎写误导别人,复制粘贴也要动一下脑子
                 
    第四种,静态方法中是不能用this的吧。。        
    这样的:
     
    还有一种利用JVM的类加载机制,
    它是线程安全的,同时又是在用到的时候才会创建(利用了java虚拟机的类加载特性)
    Java代码  
    1. public class ManagerHolder {  
    2.   
    3.     public static Manager managerHolder = new Manager();  
    4.       
    5. }  
    6.   
    7. public class Manager {  
    8.   
    9.     public static Manager getInstances() {  
    10.         return ManagerHolder.managerHolder;  
    11.     }  
    12. }  
    public class ManagerHolder {
    
        public static Manager managerHolder = new Manager();
        
    }
    
    public class Manager {
    
        public static Manager getInstances() {
            return ManagerHolder.managerHolder;
        }
    }
    
    52 楼 Mr.Cheney 2015-08-28  
    还有一种利用JVM的类加载机制,
    它是线程安全的,同时又是在用到的时候才会创建(利用了java虚拟机的类加载特性)
    Java代码  
    1. public class ManagerHolder {  
    2.   
    3.     public static Manager managerHolder = new Manager();  
    4.       
    5. }  
    6.   
    7. public class ManagerHolder {  
    8.   
    9.     public static Manager managerHolder = new Manager();  
    10.       
    11. }  
    public class ManagerHolder {
    
        public static Manager managerHolder = new Manager();
        
    }
    
    public class ManagerHolder {
    
        public static Manager managerHolder = new Manager();
        
    }
    
    
     
    第四种明显有问题嘛,静态代码块里可以访问非静态变量?静态方法里可以写this.?这不是忽悠吗,你编译能通过的        
     
    第四种明显有问题嘛,静态代码块里可以访问非静态变量?静态方法里可以写this.?这不是忽悠吗,你编译能通过的啊 
               
    第四种明显有问题嘛,静态代码块里可以访问非静态变量?静态方法里可以写this.?这不是忽悠吗,你编译能通过的啊
                
    有人说双重校验没意义
    的确在jdk1.5之前是没意义
    但在jdk的里程碑版本1.5之后就变得很有意义  而且现在以及被广泛使用  因为引入了修饰符volatile

    这也是为什么说jdk1.5才能使用双重校验
                  
    new Singleton() 如果需要传参数才能实例化怎么办?第三种方式
                
     
                 
     
     
    第四种(饿汉,变种):
    如何获取Singleton的实例?
      
    我写的一个单例, 在tomcat6上, 一个servlet put 数据,一个get数据。
    在pc 浏览器、手机的浏览器上都没有问题, 但是同事写的一个手机应用,发送请求,就发现单例变成了2个 对象。非常奇怪。不知道有没有这方面的经历??谢谢

    估计是类加载器的原因吧,像这种应用,让web服务器确保单例根本就不合适,多个web服务器更是如此,你可以采用其他方式,比如放在memcache
                   
    我写的一个单例, 在tomcat6上, 一个servlet put 数据,一个get数据。
    在pc 浏览器、手机的浏览器上都没有问题, 但是同事写的一个手机应用,发送请求,就发现单例变成了2个 对象。非常奇怪。不知道有没有这方面的经历??
                
    类是不同的加载器装载的问题怎么解决?
                  
    你的这7种方法不错。不过,最好,每个都加上私有构造方法:防止外部直接 new Singleton() 这样才能真正保证单例。。
    private Singleton(){

    }
      
    如果用第三种,建议声明为final的,因为我们没有理由不将其声明为final的。另外,关于双重锁定,建议你看看EhCache的源代码里的CacheManager类,new了新的实力后,应该在synchronized块内return,其余一致,包括将实例声明为原子的。
    附EhCache项目CacheManager部分源代码:
    Java代码  
    1. public static CacheManager create() throws CacheException {  
    2.     if (singleton != null) {  
    3.         return singleton;  
    4.     }  
    5.     synchronized (CacheManager.class) {  
    6.         if (singleton == null) {  
    7.             LOG.debug("Creating new CacheManager with default config");  
    8.             singleton = new CacheManager();  
    9.         } else {  
    10.             LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");  
    11.         }  
    12.         return singleton; // 这里是在synchronized块内返回的,而你的例子不是  
    13.     }  
    14. }  
    public static CacheManager create() throws CacheException {
        if (singleton != null) {
            return singleton;
        }
        synchronized (CacheManager.class) {
            if (singleton == null) {
                LOG.debug("Creating new CacheManager with default config");
                singleton = new CacheManager();
            } else {
                LOG.debug("Attempting to create an existing singleton. Existing singleton returned.");
            }
            return singleton; // 这里是在synchronized块内返回的,而你的例子不是
        }
    }
     
     
    cantellow
    • 浏览: 525879 次
    • 性别:
    • 来自:
    最近访客 更多访客>>
     
     
     
    最新评论
     

     
    关闭

    作者:KeerDi —— 北方的后生

    出处:http://www.cnblogs.com/keerdi/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    软件开发术语定义
    软件开发流程纲要及各个阶段产生的文档
    Java封装自己的Api
    Java中如何使封装自己的类,建立并使用自己的类库?
    Struts2中ActionContext和ServletActionContext
    TP-Link 无线路由器设置图文教程----怎么设置TP-Link无线路由器图解
    数据库(第一范式,第二范式,第三范式)
    ORACLE配置tnsnames.ora文件实例
    Windows下64位Apache服务器的安装
    公司内部Oracle RAC测试环境的简单使用说明.
  • 原文地址:https://www.cnblogs.com/123hll/p/6830082.html
Copyright © 2011-2022 走看看