惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
S
Securelist
博客园 - 聂微东
Jina AI
Jina AI
G
Google Developers Blog
J
Java Code Geeks
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
I
InfoQ
Microsoft Azure Blog
Microsoft Azure Blog
Vercel News
Vercel News
美团技术团队
aimingoo的专栏
aimingoo的专栏
爱范儿
爱范儿
雷峰网
雷峰网
T
The Blog of Author Tim Ferriss
N
Netflix TechBlog - Medium
Recent Announcements
Recent Announcements
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
C
Check Point Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Security Latest
Security Latest
The Register - Security
The Register - Security
P
Privacy International News Feed
S
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
D
Docker
P
Proofpoint News Feed
The Hacker News
The Hacker News
C
Cybersecurity and Infrastructure Security Agency CISA
The GitHub Blog
The GitHub Blog
NISL@THU
NISL@THU
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
Intezer
P
Privacy & Cybersecurity Law Blog
Spread Privacy
Spread Privacy
Latest news
Latest news
Recorded Future
Recorded Future
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Threat Research - Cisco Blogs
N
News and Events Feed by Topic
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Project Zero
Project Zero

博客园 - 可可果

Java Annotation手册 .NET环境下五种邮件发送解决方案(转载) C#中的事务处理 - 可可果 - 博客园 用commons-fileupload-1.2 实现文件上传(适用于Trubine框架) 用JAVA实现文本形式的树状结构显示(转) Google十大搜索技巧 Javascript导出Excel的方法 - 可可果 - 博客园 理解session机制 session的工作原理(转载) Session监听 button添加链接的方法 - 可可果 - 博客园 获得select控件的文本值的方法 - 可可果 - 博客园 清除缓存的方法 - 可可果 - 博客园 Java中Excel导出时文件名乱码问题的解决 java模式中的单例模式(转载) - 可可果 - 博客园 解决用户意外退出在线列表无法及时更新问题(转载) Java开发中,试用hibernate建立关联时出现:not-null property references a null or transient value异常的原因 ListBox之间进行传值的脚本的例子,一般在添加权限,行业或者多项时可以用到 - 可可果 - 博客园 Velocity基本语法
单例模式陷阱(转载)
可可果 · 2007-12-06 · via 博客园 - 可可果

今天去jdon,看了它的设计研究栏目,bang有几篇评论单例模式的文章,声称“Singleton is evil”(见http://www.jdon.com/jive/article.jsp?forum=91&thread=17578),并且引用几篇外文页面佐证自己的观点,其中有一篇文章更是说,单例不仅不是一种模式,而是一种反模式。
        下面我谈谈我对单例模式的看法。逐一分析单例模式的陷阱,帮助大家正确使用单例模式。
(1) 陷阱一:调用函数的性能瓶颈
        在c++中,单例只有一种实现方式——LazySingleton, 实现如下(本文全部使用java代码):

public   class  LazySingleton {
    
private   static  LazySingleton m_instance = null ;
     private LazySingleton(){};
     synchronized public static LazySingleton getInstance(){
        
if(m_instance==null)
            m_instance
=new LazySingleton();
        
return m_instance;
    }

}

LazySingleton 将对象的初始化推迟到调用的时候。并且为了防止多线程环境下产生多个实例,使用synchronized关键字保证函数getInstance调用的线程 安全。synchronized关键字的存在保证了只会产生一个对象,但也成了多线程环境下的性能瓶颈。一个多线程的程序,到了这里却要排队等候成了一个 单线程式的执行流程,这在高并发环境下是不可容忍的。而c++中可以使用双重检查机制将这种性能问题仅仅限制在第一次构造对象的时候,而java中不可以 使用双重检查机制。
        但是java可以实现EagerSingleton,实现如下:

public   class  EagerSingleton {
    
private   static  EagerSingleton m_instance = new  EagerSingleton();
     private EagerSingleton(){};
     public static agerSingleton getInstance(){
        
return m_instance;
    }

}

与LazySingleton相比,EagerSingleton将对象的初始化放到了类加载的时候。这样就避免了synchronized关键字的性能瓶颈。
(2)陷阱二:访问互斥共享资源
         EagerSingleton中访问互斥资源也要考虑线程安全问题。下面看一个例子:

public class EagerSingleton{
    
private static EagerSingleton m_instance=new EagerSingleton();
    
private HashMap map=new HashMap();
   
private EagerSingleton(){};
   
public static agerSingleton getInstance(){
        
return m_instance;
    }

       
public void refreshMap(Object key){
        
synchronized(map){
            
if(!map.contains(key))
                map.put(key,value);
//value为此时的实时数据
        }
 
    }

}

因为该类是单例,可能多线程并发访问map,map非线程安全,需要加线程安全关键字,否则就掉入了访问互斥资源的陷阱。
(3)陷阱三:非法逻辑陷阱
        这种情况一般是滥用单例模式造成的,下面考虑一种滥用单例的情况。下面的代码的作用是getValueByName后,马上printValue即完成操作流程。

public class EagerSingleton{
    
private static EagerSingleton m_instance=new EagerSingleton();
    
private String value=null;
   
private EagerSingleton(){};
   
public static agerSingleton getInstance(){
        
return m_instance;
    }

    
synchronized public void getValueByName(String name){
        value
=getByNameFromDateBase(name);
        
    }

    
public viod printValue(){
        System.out.println(
this.vaue);
    }

}

该类含有一私有属性value,在多线程环境下不能保证value值的合理逻辑,一线程getValueByName后,马上printValue,也有可能value的值已经被其他线程修改。这种情况就属于单例模式的滥用,该类根本不适合做成单例。
        消除非法逻辑的陷阱,可以通过将该类重构为纯粹的行为类完成。重构后的代码如下:

public class EagerSingleton{
    
private static EagerSingleton m_instance=new EagerSingleton();
   
private EagerSingleton(){};
   
public static agerSingleton getInstance(){
        
return m_instance;
    }

    
private String getValueByName(String name){
        
return getByNameFromDateBase(name);
        
    }

    
public viod printName(String name){
        String value
=getValueByName(String name);
        System.out.println(value);
    }

}

通过调用printName(String name)直接完成操作流程,将其中的私有属性处理成过程式的参数传递,将该类修改成纯粹的行为类。

        含有私有属性并且含有对它赋值操作的类并非都会调入该陷阱,构造函数里进行对私有属性赋值不会引起非法逻辑,如下代码

public class EagerSingleton{
    
private static EagerSingleton m_instance=new EagerSingleton();
    
private HashMap map==new HashMap();
    
    private
 EagerSingleton(){
        map.put(key,value);
//value为此时的实时数据
    }

   
public static agerSingleton getInstance(){
        
return m_instance;
    }

}

构造函数里不必要加线程安全关键字也可以保证线程安全,因为类加载器是线程安全的,EagerSingleton只会在类加载的时候实例化一次,这样不会出现单例模式的线程不安全,也不会造成非法逻辑。
(4)陷阱四:单例陷阱的传递
        当含有对象作为单例类的私有属性时,陷阱不仅会出现在该类本身,还会传递到私有对象所在的类中。看如下代码:

public class EagerSingleton{
    
private static EagerSingleton m_instance=new EagerSingleton();
    
private NewClass newClass=nll;
   
private EagerSingleton(){
        newClass
=new NewClass();
    }
;
   
public static agerSingleton getInstance(){
        
return m_instance;
    }

   
public viod printName(String name){
        String value
=newClass.operationByNameAndReturnValue(String name);
        System.out.println(value);
    }

}

乍 一看,代码中除了构造函数对私有属性进行了初始化操作,其他地方没有对私有属性的赋值,不会引起非法逻辑陷阱。其实这个赋值操作可能隐含在 newClass.operationByNameAndReturnValue(String name)操作,只有保证了NewClass的operationByNameAndReturnValue操作不会对它的私有属性赋值操作,才能保证真 正的合理逻辑。同样,只有保证NewClass的operationByNameAndReturnValue操作没有掉入访问互斥资源陷阱,才能真正保 证EagerSingleton没有掉入该陷阱。
        消除该陷阱的方法:(1)类方法的名称要合理,比如纯粹的行为方法名:interprete,excute,operation之类的方法中就不该含有对 私有属性直接或者间接的赋值操作,每个方法的责任要明确。(2)单例类中尽量不要含有非单例类的实例作为私有属性(容器类除外),一定要有类的实例作为私 有属性的时候,重新审视这个作为私有属性的类,是不是也应该设计成单例类;或者保证对它的初始化赋值限制在构造函数内。