`
jameswxx
  • 浏览: 776510 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

说说单例模式

    博客分类:
  • java
阅读更多

      单例模式?多么简单!也许吧,可是要通过简单的现象,看到问题的本质,就比较难,知其然而不知其所以然,这种态度不好。

 

一:看看最简单的

public class SingleInstance{
  private static  instance=new SinleInstance();

  public static SingleInstance getInstance(){
   return instance;
  }
}

 

这个很明了,也确实会返回唯一的实例,但是如果我永远都不会用到SingleInstance.getInstance(),instance这个对象却一直存在,占用了内存空间,浪费。

 

二:那就不先实例化

public class SingleInstance{
  private static  SingleInstance instance;

  public static  SingleInstance getInstance(){
   if(instance==null){
      instance=new SingleInstance();
   }
   return instance;
  }
}

表面上,这个似乎是可行的。但是很显然,在多线程并发环境中,可能会创建两个instance,也有可能一个线程拿到的instance是一个不完整的实例。

 

三:好吧,那我加上同步总可以吧

public class SingleInstance{
  private static  SingleInstance instance;

  public static synchronized SingleInstance getInstance(){
   if(instance==null){
      instance=new SingleInstance();
   }
   return instance;
  }
}

这个虽然不会有内存上的浪费,但是有同步锁住了整个class,会降低系统的性能。按照数据结构的说法,就是一个提高了空间复杂度,一个提高了时间复杂度。

 

四:最佳实践

比较好的做法 

public class SingleInstance {
    private static class InstanceHolder {
        public static final SingleInstance instance = new SingleInstance();
    }

    public static SingleInstance getInstance() {
        return InstanceHolder.instance;
    }
}

这种Holder做法保证了实例在用到的时候才会创建,而且创建的实例肯定是唯一的。


五:所谓的DCL
下面这种做法是广为流传的所谓DCL(双重检查锁定)做法,这是没有正确理解java内存模型造成的,只保证了创建实例的原子性,而没有解决共享资源的可见性。

public class SingleInstance{
  private static  SingleInstance instance;

  public static  SingleInstance getInstance(){
   if(instance==null){
       synchronized(SingleInstance.class){
           if(instance==null){
               instance=new SingleInstance();
           }
       }
   }
   return instance;
  }
}




表面上看,这个似乎确实没有什么问题呢,其实还是有问题的,虽然概率比较小,要深刻的理解为什么,必须要对java内存模型比较熟悉。对于instance的创建,某个时刻某个线程拿到了同步锁,将instance创建了,并且及时写回了主存。但是对于另一个线程而言,它不能及时“看到”instance变量的最新值。它的instance可能是工作内存里的而不是主内存里的,甚至更为糟糕的情况是,instance是主内存的,可它的某些属性是工作内存的,这会导致程序发生莫名其妙不可捉摸的错误。在平台级框架里,这会造成系统在高并发环境下不稳定,甚至造成致命的错误

分享到:
评论
10 楼 mengsina 2012-05-18  
楼主的意思是:?
1.线程A进入getInstance()方法;
2.线程B进入getInstance()方法;
3.线程A执行,if(instance==null),从主存拷贝instance到工作内存;
4.线程B执行,if(instance==null),从主存拷贝instance到工作内存;
5.线程A进入临界区:

synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }


执行完后,线程A同步至主存,

接下来就是楼主担心的吗?

就是“该线程放弃锁后,后续执行的线程执行if(instance==null)时,instance不一定是主内中最新的”

可问题是,如果对SingleInstance.class加锁,上述步骤中的2,4会发生吗?即使在一个高度并发的情况下?

再问:

如果对SingleInstance.class加锁,线程A获取到了锁,那么线程B是不是就需要等待,即使高度并发?

如果线程B需要等待,那么线程A执行的就是原子性动作,等线程B去执行时,获取到的自然就是线程A同步好的instance,应该不会出现楼主担心的情况,不知道是不是这样?

我测试了一下,你说的是对的,不会出现楼主担心的情况。
但是,锁住的不是整个方法,所以上述步骤中的2 发生,即每次读的值 是工作内存的,可能是和其他线程修改后的值不一样,即旧值。  
步骤 4不会发生。每次 4 都是从主内存中拷贝一份新的。即其他线程修改后的新值。
9 楼 sunxian99 2012-02-01  
dcl 没有解决共享资源的可见性 那加上volatile 呢 这个实例就是在主内存中了
8 楼 huhu_long 2011-10-28  
trunk 写道
第一种怎么浪费啦。。。。不理解啊。你不调用SingleInstance.getInstance(),那么private static  instance=new SinleInstance()应该就不会被执行啊,那么就不会new SinleInstance()啊,难道在SingleInstance类加载的时候 就对静态的属性初始化了? 可是类加载有好几种方式啊不一定加载的时候就对静态变量赋值...,楼主能否解析下疑问啊......


同意,这里如果你不去调用getInstance()的话应该是不会初始化的。 当 setstatic, putstatic, invokestatic等指令被调用的时候才会去初始化类。 所以如果不去调用getInstance()方法的话,instance将仍然维持零值NULL.

请主人confirm.
7 楼 regbin 2011-09-27  
DCL下
private static  SingleInstance instance;  

加volatile可以解决问题
6 楼 wupuyuan 2011-09-05  
我觉得恶汉模式就可以,毕竟产生出来空间不大,而且,如果生产出来不用的话是设计的问题!
5 楼 swinginginging 2011-09-01  
jameswxx 写道
ryuluck 写道


 if(instance==null){   
       synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }   
   } 



临界区这里的instance,难道不是从主内存拷贝到工作内存里面的吗?


进入临界区这里的线程,instance是从主内存拷贝到工作内存。
该线程放弃锁后,后续执行的线程执行 if(instance==null)时,instance不一定是主内中最新的。


楼主的意思是:?
1.线程A进入getInstance()方法;
2.线程B进入getInstance()方法;
3.线程A执行,if(instance==null),从主存拷贝instance到工作内存;
4.线程B执行,if(instance==null),从主存拷贝instance到工作内存;
5.线程A进入临界区:

synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }


执行完后,线程A同步至主存,

接下来就是楼主担心的吗?

就是“该线程放弃锁后,后续执行的线程执行if(instance==null)时,instance不一定是主内中最新的”

可问题是,如果对SingleInstance.class加锁,上述步骤中的2,4会发生吗?即使在一个高度并发的情况下?

再问:

如果对SingleInstance.class加锁,线程A获取到了锁,那么线程B是不是就需要等待,即使高度并发?

如果线程B需要等待,那么线程A执行的就是原子性动作,等线程B去执行时,获取到的自然就是线程A同步好的instance,应该不会出现楼主担心的情况,不知道是不是这样?
4 楼 zuozhengfeng 2011-07-21  
jameswxx 写道
ryuluck 写道


 if(instance==null){   
       synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }   
   } 



临界区这里的instance,难道不是从主内存拷贝到工作内存里面的吗?


进入临界区这里的线程,instance是从主内存拷贝到工作内存。
该线程放弃锁后,后续执行的线程执行 if(instance==null)时,instance不一定是主内中最新的。


确实不太理解这段代码会出现什么样的问题,为什么会有这样的问题!作者有心的话可以写出一个浅显易懂的文章分享!
3 楼 trunk 2011-07-18  
第一种怎么浪费啦。。。。不理解啊。你不调用SingleInstance.getInstance(),那么private static  instance=new SinleInstance()应该就不会被执行啊,那么就不会new SinleInstance()啊,难道在SingleInstance类加载的时候 就对静态的属性初始化了? 可是类加载有好几种方式啊不一定加载的时候就对静态变量赋值...,楼主能否解析下疑问啊......
2 楼 jameswxx 2011-05-24  
ryuluck 写道


 if(instance==null){   
       synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }   
   } 



临界区这里的instance,难道不是从主内存拷贝到工作内存里面的吗?


进入临界区这里的线程,instance是从主内存拷贝到工作内存。
该线程放弃锁后,后续执行的线程执行 if(instance==null)时,instance不一定是主内中最新的。
1 楼 ryuluck 2011-05-24  


 if(instance==null){   
       synchronized(SingleInstance.class){   
           if(instance==null){   
               instance=new SingleInstance();   
           }   
       }   
   } 



临界区这里的instance,难道不是从主内存拷贝到工作内存里面的吗?

相关推荐

    运算符单例友元重载作业

    首先,我们要理解什么是“单例模式”。单例模式是一种设计模式,确保一个类只有一个实例,并提供全局访问点。这种模式常用于控制共享资源,如数据库连接、日志文件或线程池。在C++中,单例通常通过私有构造函数和...

    面向对象模式

    再来说说**单例模式**。单例模式确保一个类只有一个实例,并提供一个全局访问点。在PHP中,单例常用于管理共享资源,如数据库连接、缓存系统等。通过单例,我们可以控制类的实例化过程,防止因过多实例化导致的资源...

    C#设计模式_11138467

    C#中实现单例模式通常有懒汉式和饿汉式两种方式,前者在第一次使用时初始化,后者在类加载时就完成初始化。 接下来是工厂模式,它是创建型设计模式的一种,提供了创建对象的接口,但隐藏了具体的创建过程。在C#中,...

    设计模式介绍chm

    接着是单例模式,确保一个类只有一个实例,并提供全局访问点。在资源管理或者需要全局共享状态的情况下,单例模式非常有用。但要注意,过度使用单例可能导致代码过于紧密耦合,不易测试和维护。 接下来,我们来看看...

    2023年最新java面试大全

    【06期】单例模式有几种写法? 【07期】Redis中是如何实现分布式锁的? 【08期】说说Object类下面有几种方法呢? 【09期】说说hashCode() 和 equals() 之间的关系? 【10期】Redis 面试常见问答 【11期】分布式...

    Struts2和Struts1的区别,详细点,主要说说Struts2

    - **Struts1**中的Action对象在整个请求周期内是单例模式的,这意味着所有请求都会共用同一个Action实例,这可能会导致线程安全问题。 - 相比之下,**Struts2**为每个请求创建一个新的Action实例,这不仅提高了安全...

    28_你能说说redis的并发竞争问题该如何解决吗?.zip

    1. **单例操作**:尽可能将多步操作封装为单次命令,如Redis的`INCRBYFLOAT`命令可以实现浮点数的原子性增加。 2. **Lua脚本**:通过Lua脚本来组合多个操作,并利用`EVAL`命令执行,保证整个脚本的原子性。例如,...

    Java后端技术面试汇总-2019

    - **常见的设计模式**:单例模式、工厂模式、策略模式、观察者模式等。 - **设计模式的六大原则**:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则。 - **常见的单例模式**:...

    Spring MVC面试宝典1.pdf

    在SpringMVC中,控制器通常被定义为单例模式,这意味着所有请求都共享同一个控制器实例。因此,需要注意解决线程安全问题。主要通过以下几种方式: - **非线程安全的成员变量**:确保控制器中不包含任何实例级别的...

    当 Kubernetes 遇到 GPT:利用编程模型的秘诀,让你省时省力!

    一说到编程模型或者开发模式,大家应该第一反应是23 种开发模型:单例、工厂、发布订阅... 吧啦吧啦。实际我也是,但是这次想说的不是他们,而是声明式编程(declarative programming)范式和陈述式编程(imperative...

    java核心面试技术点

    线程 单例模式下的线程安全http://blog.sina.com.cn/s/blog_75247c770100yxpb.html map集合 集合数据结构及方法的使用 多线程 线程进程 线程状态 线程状态的改变 sql :临时表、游标、存贮过程、触发机制...

    iOS-面试宝典3.0.pdf

    单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。其实现通常包括私有构造函数、静态实例变量以及静态工厂方法。 #### 六、什么是动态,举例说明 动态性是指程序在运行时能够改变其结构...

    超级有影响力霸气的Java面试题大全文档

    超级有影响力的Java面试题大全文档 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。...

    java面试题

    如何控制两种框架中的单例模式? 74 73. Spring 75 73.1. Spring 简介 75 73.2. 为什么要用Spring? 76 73.3. spring工作机制或工作原理 76 73.4. Spring是什么?根据你的理解详细谈谈你的见解。 76 73.5. 项目中如何...

    java笔试题

    - **单例模式**:确保一个类只有一个实例,并提供一个全局访问点。 - **工厂模式**:定义创建对象的接口,但让子类决定实例化哪个类。 - **观察者模式**:定义了一种一对多的依赖关系,当一个对象的状态发生改变时,...

    Java框架面试题总结

    - **singleton**:单例模式,容器中只有一个Bean实例。 - **prototype**:原型模式,每次请求都会创建一个新的Bean实例。 - **request**:每个HTTP请求都有一个Bean实例。 - **session**:每个HTTP Session都有一个...

    Delphi最新三层源码

    采用的是单例模式。 之后具体的调用,相关代码如下: if FindClass(fClsPer.ClassName) <> nil then begin tmpPer := TPersistentClass(FindClass(fClsPer.ClassName)).Create; Supports(tmpPer, StringToGUID('...

Global site tag (gtag.js) - Google Analytics