cuiyi's blog(崔毅 crazycy)

记录点滴 鉴往事之得失 以资于发展
数据加载中……

单例模式(SingLeton Pattern)的误区

单例(SingLeton)故名思义就是在一个JVM运行中只有一个对象存在;请你务必注意到是在一个JVM虚拟机内。

今天一个朋友问过这样一个问题:为什么他的单例每次都进入构造函数,程序如下:

public class ServerThread implements Runnable{

     
private static ServerThread instance = null;  

     
private ServerThread() {

         
try {

             
if (socket == null) {

                 socket 
= new DatagramSocket(Constants.SERVER_PORT);

             }

          } 
catch (Exception e) {

                 e.printStackTrace();

           }

       }

     
public static final ServerThread getInstance() {

        
if (instance == null) {
           System.out.println(
"instance is null new generate.");
           instance 
= new ServerThread();
        }
        
return instance;

      }

    
public Thread getThreadInstance() {

        
if (threadInstance == null) {

            threadInstance 
= new Thread(instance);
        }
        
return threadInstance;
    }

。。。。。。。。

}

public class Main {

      main函数有servTr 
= ServerThread.getInstance().getThreadInstance();一句

代码没有问题吧;为什么他的单例每次都进入构造函数呢?

细问之下才知道了他启动了两次Main类;启动一个Main类便在一个JVM,两次的单例怎么能是同一个呢?!

记住:是在同一个JVM中唯一,不是在一个物理内存中唯一。当然后者不是不能做到的,分布式对象嘛,呵呵。


第二点就是:单例的构造:

目前方式多种,我认为只有两种是正确的:

第一:private final static DaoFactory instance = new DaoFactory();

第二:

private static DaoFactory instance = null;

      
private Properties props = null;

       
private Map daos = null;

      
private DaoFactory() {

           props 
= new Properties();

           daos 
= new HashMap();

      }

     

      
public static DaoFactory getInstance() {

           
while (null == instance) {

                 
synchronized(DaoFactory.class) {

                      
if (null == instance) {

                            instance 
= new DaoFactory();

                            instance.initDaoFactroy();

                      }

                 }

           }

           
return instance;

      }


看清楚了,这里用的是while;不是if

为什么不能用if呢?下面的一个例子说明

public class DomainFactory {

      
private List provinces;
 
           
private Map domains;

      
private static DomainFactory instance;

      
private DomainFactory() {

           generateDomainTree();

      }

      
public static DomainFactory getInstance() {

           
if (null == instance) {

                 
synchronized (DomainFactory.class) {

                      
if (null == instance) {

                            instance 
= new DomainFactory();

                      }

                 }

           }

           
return instance;

      }

      
private void generateDomainTree() {

。。。。。

}


 

public class CategoryFactory {

      
private Map map;     

      
private static CategoryFactory instance;
     

      
private CategoryFactory() {

           map 
= new HashMap();

           generateCategoryTree();

      }     

      
public static CategoryFactory getInstance() {

           
if (null == instance) {

                 
synchronized (CategoryFactory.class) {

                      
if (null == instance) {

                            instance 
= new CategoryFactory();

                      }

                 }

           }

           
return instance;

      }     

      
private void generateCategoryTree() {

。。。。。。

}

 

public class SearchAction extends DispatchAction {

      
public ActionForward getCatalogData(ActionMapping mapping, ActionForm form,

                 HttpServletRequest request, HttpServletResponse response) {

           DomainObject domainObject 
= new DomainObject();

           domainObject.setCity(
"b");

           request.getSession().setAttribute(
"domainObject", domainObject);

           request.setAttribute(
"domains", DomainFactory.getInstance().getCity(domainObject.getCity()));

           
return mapping.findForward("left");

      }    
 

      
public ActionForward getCatalogDataCategory(ActionMapping mapping, ActionForm form,

                 HttpServletRequest request, HttpServletResponse response) {

           request.setAttribute(
"productCategories", CategoryFactory.getInstance().getRootProductCategories());

           
return mapping.findForward("body");

      }

<frameset rows="80,668*" cols="*" frameborder="no" border="0" framespacing="0">

    
<frame name="header" src="./search_header.jsp" scrolling="no" title="header" noresize="noresize">

    
<frameset cols="180,*" frameborder="yes" border="0" framespacing="0">

       
<frame name="left" scrolling="no" src="search.html?method=getCatalogData" target="_self" noresize="noresize">

       
<frame name="body" scrolling="yes" src="search.html?method=getCatalogDataCategory" scrolling="yes" target="_self" noresize="noresize">
  
</frameset>
</frameset>

用if 出错了吧?为什么,同时进入了if(null == instance)的判断,然后争夺锁;结果争夺不到的只能在争夺到后容易出现问题,具体这里说不清楚,希望明白人跟贴进一步讨论之

posted on 2006-07-15 00:02 crazycy 阅读(2520) 评论(13)  编辑  收藏 所属分类: Design Pattern、JEE Pattern

评论

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

改为
public synchronized static DaoFactory getInstance() {
if(instance == null) {
instance = new DaoFactory();
instance.initDaoFactroy();
}
}
}
2006-07-15 08:00 | badqiu

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

我想问问,在一个web server,比如tomcat下,放两个application。
在这两个application中都包含有同一个单实例的class。那么这两个单实例会互相有影响么?为什么在web系统中很少见人使用单实例,实际的例子就是spring取对象的方式,直接做一个单实例封装一下就可以在系统内方便的取对象了,为什么还要用WebApplicationContextUtils.getRequiredWebApplicationContext(context)的方式来取,我看代码spring 是把启动时加载的配置信息放在ServletContext里,为什么不直接放在一个单实例里取值还方便,对单实例一直有疑惑,还请老兄赐教
2006-07-15 08:16 | mixlee

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@mixlee
两个application的classpath不一样,放在WEB-INF里面的lib是不能共享的,spring的ApplicationContext是跟具体的webapp相关的,当然不能做成全局的单例,只能做成一个ServletContext里面的单例
2006-07-15 12:03 | quaff

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

关于单例
懒汉和饿汉式的是最安全的.这里有篇文章不妨参考一下
http://www-128.ibm.com/developerworks/java/library/j-dcl.html?dwzone=java
2006-07-15 12:39 | binge

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@badqiu
呵呵

多谢多谢

今天我用while也遇到了和if一样的问题
看样子,的确是单例实现方式的问题
现在我用你推荐的方案改进一下
2006-07-15 16:19 | crazycy

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@binge
我用的就是你推荐的文章的Double-checked locking哦

奇怪了 我这个地方是同时抢占;而且是肯定发生的;

哎...以前一直这么用,只是取instance的线程同时抢占的几率小,呵呵,问题的深入解决看来还需要多过河才行(多过河找到不湿脚的办法,呵呵)啊
2006-07-15 16:23 | crazycy

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@quaff
既然两个没影响,那做成单例有什么不可,反正各自加载各自的配置文件,互不影响。
2006-07-15 21:25 | mixlee

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@crazycy
nono,文章写到:
The issue of the failure of double-checked locking is not due to implementation bugs in JVMs but to the current Java platform memory model. The memory model allows what is known as "out-of-order writes" and is a prime reason why this idiom fails.

文章中说,基于java的内存模型,DCL并不是最安全的.最安全的是饿汉 和懒汉.
当然,懒汉很影响效率.
2006-07-15 22:28 | binge

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

记住:是在同一个JVM中唯一,不是在一个物理内存中唯一。当然后者不是不能做到的,分布式对象嘛,呵呵。


看了很受启发,想问一下,如果我希望在一个物理内存中唯一,甚至在多个应用服务器的cluster环境中唯一,有什么方法吗?
2006-08-24 14:25 | merrymei

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@merrymei

好像也提供不出解决方案.......
2006-09-01 10:51 | crazycy

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

DCL方式在java里是行不通的。 希望不要误导别人
2006-09-20 14:42 | freshman

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

@freshman
接受建议
2006-09-20 22:23 | crazycy

# re: 单例模式(SingLeton Pattern)的误区  回复  更多评论   

使用 synchronized 关键字就可以解决锁争夺的问题
2006-10-11 09:44 | www.坑你.net

只有注册用户登录后才能发表评论。


网站导航: