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

Java设计模式之单例模式

    博客分类:
  • java
阅读更多

此文章转至http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html

 

        详细的单例模式分析

   首先来看一个典型的实现:

 
 /**
* 基础的单例模式,Lazy模式,非线程安全
* 优点:懒加载,初次使用时实例化单例,避免资源浪费
* 缺点: * 1、懒加载,如果实例初始化非常耗时,初始使用时,可能造成性能问题
* 2、非线程安全。多线程下可能会有多个实例被初始化。
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton () {
}
public static Singleton getInstance() {
if (singleton == null) { // 1
singleton = new Singleton (); // 2
}
return singleton ;
}
}
 

  为什么此种创建会导致非线程安全和多线程的运行可能创建两个对象呢?

  1、当线程A进入到标记(#1)时,检查singleton是否为空,此时是空的。
  2、这时线程A还没有创建对象,线程B也进入到标记(#1)。切换到线程B执行。同样检查singleton为空,于是往下执行标记(#2),创建了一个实例。

  3、切换回线程A,由于之前检查到singleton为空。所以也会执行标记(#2)创建实例。
  4、线程A、B都创建对象。

 如何实现线程安全呢?

  方法一:同步方法。即在getInstance()方法上加上synchronized关键字。  

 
 /**
* 同步获取单例对象的方法,懒加载模式,线程安全
* 优点:
* 1、懒加载,初次使用时实例化单例,避免资源浪费
* 2、线程安全
* 缺点:
* 1、懒加载,如果实例初始化非常耗时,初始使用时,可能造成性能问题
* 2、每次调用getInstance()都要获得同步锁,性能消耗。
*/
public class Singleton {

private static Singleton singleton = null;
private Singleton() {
}
/**
* 同步方法,实现线程互斥访问,保证线程安全。
*/
public static synchronized Singleton getInstance() {
if (singleton == null) { // 1
singleton = new SingletonTwo(); // 2
}
return singleton ;
}

}
 

 

  加上synchronized后确实实现了线程的互斥访问getInstance()方法。从而保证了线程安全。但这种设计,会导致问题的只是当singleton 还没有被实例化的时候,多个线程访问#1的代码才会导致问题。而当singleton 已经实例化完成后。每次调用getInstance(),其实都是直接返回的。即使是多个线程访问,也不会出问题。但给方法加上synchronized后。所有getInstance()的调用都要同步了。其实我们只是在第一次调用的时候要同步。而同步需要消耗性能。

方法二:双重检查加锁。
  
其实经过分析发现,我们只要保证singleton  = new Singleton(); 是线程互斥访问的就可以保证线程安全了。那把同步方法加以改造,只用synchronized块包裹这一句。就得到了下面的代码:

 
     public static Singleton getInstance() {
if (singleton  == null) { // 1
synchronized (Singleton.class) {
singleton  = new Singleton(); // 2
}
}
return singleton  ;
}
 

 

  但是这种设计同样不能满足我们的需求

  1、线程A和线程B同时进入//1的位置。这时singleton  是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、线程A、B同样都创建对象。 

  为了解决这个问题。我们需要在//2的之前,再加上一次检查singleton  是否被实例化。(双重检查加锁)接下来,代码变成了这样:

 
     public static Singleton  getInstance() {
if (singleton  == null) { // 1
synchronized (Singleton .class) {
if (singleton  == null) {
singleton  = new Singleton(); // 2
}
}
}
return singleton  ;
}
 

 

  这样,当线程A返回,线程B进入synchronized块后,会先检查一下singleton  实例是否被创建,这时实例已经被线程A创建过了。所以线程B不会再创建实例,而是直接返回。这个问题已经被我们完美的解决了。遗憾的是,事实完全不是这样!这个方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:singleton  =  new Singleton(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给singleton  这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,singleton  已经被设置为非空了。下面我们看一下出问题的过程:
  1、线程A进入getInstance()方法。
  2、因为此时singleton  为空,所以线程A进入synchronized块。
  3、线程A执行singleton  =  new Singleton();  把实例变量singleton  设置成了非空。(注意,是在调用构造方法之前。)
  4、线程A阻塞,此时线程B进入。
  5、线程B检查singleton  是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回singleton  的引用。(问题出现了,这时singleton  的引用并不是Singleton的实例,因为没有调用构造方法。) 
  6、线程B退出,线程A接着调用构造方法创建实例,再返回

 解决由“无序写”带来的问题。

 
     public static Singleton  getInstance() {
if (singleton  == null) {
synchronized (Singleton .class) { // 1
Singleton temp = singleton  ; // 2
if (temp == null) {
synchronized (Singleton .class) { // 3
temp = new Singleton (); // 4
}
singleton  = temp; // 5
}
}
}
return singleton  ;
}
 

 

  解释一下执行步骤。
  1、线程A进入getInstance()方法。
  2、因为singleton 是空的 ,所以线程A进入位置//1的第一个synchronized块。
  3、线程A执行位置//2的代码,把singleton  赋值给本地变量temp。singleton  为空,所以temp也为空。 
  4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
  5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题) 
  6、线程A阻塞,线程B进入getInstance()方法。
  7、因为singleton  为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
  8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
  9、将temp的实例引用赋值给singleton  。退出两个synchronized块。返回实例。
  10、线程B激活,进入第一个synchronized块。
  11、线程B执行位置//2的代码,把singleton  实例赋值给temp本地变量。
  12、线程B判断本地变量temp不为空,所以跳过if块。返回singleton  实例。

  好吧,问题终于解决了,线程安全了。但是我们的代码由最初的3行代码变成了现在的一大坨~。于是又有了下面的方法。

  方法三:预先初始化static变量。

 
 /**
* 预先初始化static变量 的单例模式 非Lazy 线程安全
* 优点:
* 1、线程安全
* 缺点:
* 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
*/
public class Singleton {

private static Singleton singleton  = new Singleton();
private Singleton () {

}
public static Singleton getInstance() {
return singleton  ;
}

}
 

  看到这个方法,世界又变得清净了。由于java的机制,static的成员变量只在类加载的时候初始化一次,且类加载是线程安全的。所以这个方法实现的单例是线程安全的。但是这个方法却牺牲了Lazy的特性。单例类加载的时候就实例化了。如注释所述:非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

  那到底有没有完美的办法?懒加载,线程安全,代码简单。

  方法四:使用内部类。

 

 
 /**
* 基于内部类的单例模式 Lazy 线程安全
* 优点:
* 1、线程安全
* 2、lazy
* 缺点:
* 1、待发现
*/
public class Singleton {

/**
* 内部类,用于实现lzay机制
*/
private static class SingletonHolder{ private static Singleton singleton  = new Singleton();
}
private Singleton () {

}
public static Singleton getInstance() {
return SingletonHolder.singleton  ;
}

}
 

  解释一下,因为java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次singleton  。

 

  最后,总结一下:
  1、如果单例对象不大,允许非懒加载,可以使用方法三。
  2、如果需要懒加载,且允许一部分性能损耗,可以使用方法一。(官方说目前高版本的synchronized已经比较快了)
  3、如果需要懒加载,且不怕麻烦,可以使用方法二。
  4、如果需要懒加载,没有且!推荐使用方法四。 

 

0
0
分享到:
评论
2 楼 xfxlch 2014-09-08  
1、线程A进入getInstance()方法。
  2、因为此时singleton  为空,所以线程A进入synchronized块。
  3、线程A执行singleton  =  new Singleton();  把实例变量singleton  设置成了非空。(注意,实在调用构造方法之前。)
  4、线程A退出,线程B进入。
  5、线程B检查singleton  是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回singleton  的引用。(问题出现了,这时singleton  的引用并不是Singleton的实例,因为没有调用构造方法。)
  6、线程B退出,线程A进入。
  7、线程A继续调用构造方法,完成singleton  的初始化,再返回。


从 第6步开始是不是有问题啊。这里线程B都推出了,线程A怎么可能又开始进入了,线程A不是在第4部就退出了吗,你再进入也不可能进入到实例化那一步啊,singleton早就不为空了
1 楼 zhukewen_java 2014-09-08  
怎么这么多讨论单例的,讨论来讨论去都一样,也没有什么新意样

相关推荐

    Java设计模式之单例模式的七种写法

    Java设计模式之单例模式的七种写法 单例模式是一种常见的设计模式,它确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机的驱动程序对象常...

    JAVA设计模式之单例模式(完整版)1[定义].pdf

    JAVA设计模式之单例模式(完整版)1[定义].pdf

    java设计模式之单例模式详解

    Java设计模式之单例模式详解 一、单例模式概览 单例模式(Singleton Pattern)是面向对象设计模式中的一种,属于创建型模式。它确保一个类仅有一个实例,并提供一个全局访问点来访问该实例。单例模式的核心在于控制...

    JAVA设计模式之单例模式

    单例模式是软件设计模式中的一种经典模式,它在Java编程中被广泛使用,尤其是在需要控制实例化过程,或者确保某类只有一个实例时。本文将深入探讨Java中的单例模式,帮助你理解其原理和应用。 单例模式的核心思想是...

    java设计模式之单例模式

    Java设计模式中的单例模式是一种常用的创建型设计模式,它保证了类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置信息等。接下来,我们将深入探讨8种不同的单例...

    Java设计模式之单例模式讲解

    入名所示,该文件为最详细的Java单例模式讲解并附有讲解代码。主要讲了单例模式的几种方法,懒汉模式、饿汉模式、静态内部类模式。着重讲解了懒汉模式下怎么实现线程安全。饿汉模式和静态内部类模式如何设置能够避免...

    java设计模式之单例模式.zip

    Java设计模式是面向对象编程中的重要概念,它们是软件开发中经过验证的、解决常见问题的最佳实践。在这些模式中,单例模式是最为广泛使用的一种。单例模式确保一个类只有一个实例,并提供一个全局访问点,使得在整个...

    java设计模式之单例模式.pptx

    给同学上课时做的ppt

    JAVA设计模式之单例模式(完整版)[归类].pdf

    **Java设计模式:单例模式详解** 单例模式是软件设计中的一种常见模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在控制资源的共享、提高性能或协调多个组件之间的交互等方面有着广泛的应用。 ##...

    01.Java设计模式之 单例模式.pdf

    项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

    Java设计模式之单例模式实例详解【懒汉式与饿汉式】

    单例模式是Java设计模式中的一种,主要用于控制对象的实例化,确保整个应用程序中只有一个对象实例,并提供了一个全局的访问点。单例模式的主要优点是能够避免对象的多次实例化,节省系统资源,并能够提供一个统一的...

    Tom_20170324_Java设计模式之单例模式的七种写法1

    2、单例类必须自己创建自己的唯一实例 3、单例类必须给所有其他对象提供这一实例 2、资源加载和性能:饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会

    设计模式之单例模式(结合工厂模式)

    单例模式是软件设计模式中的一种经典模式,它保证了类只有一个实例存在,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。结合工厂模式,...

    设计模式单例模式和工厂模式综合应用

    "设计模式单例模式和工厂模式综合应用"的主题聚焦于两种常用的设计模式:单例模式和工厂模式,并探讨它们如何协同工作来实现高效、灵活的代码结构。这个主题尤其适用于Java编程语言,因为Java的面向对象特性使得设计...

    Java设计模式-单例模式详解

    Java设计模式-单例模式详解 单例模式是 Java 设计模式中的一种常用的设计模式,旨在保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式的目的是为了保证在一个进程中,某个类有且仅有一个实例。 ...

    计算机后端-Java-图解java设计模式037 单例模式JK.avi

    计算机后端-Java-图解java设计模式037 单例模式JK.avi

    java 设计模式之单例模式

    Java中的单例模式是一种设计模式,它用于控制类的实例化过程,确保在整个应用程序中,一个类只有一个实例存在。这在处理全局资源、缓存、线程池等场景时非常有用,因为它可以避免多个实例之间的数据冲突,节省系统...

    全面解析Java设计模式之单例模式

    单例模式是设计模式中的一种基础模式,它确保一个类在任何情况下只有一个实例存在。这种模式的核心在于限制类的实例化,保证全局范围内只有一个对象,同时也提供了一个全局访问点,使得其他部分的代码能够方便地使用...

Global site tag (gtag.js) - Google Analytics