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

(转)java 单例模式

 
阅读更多

转自http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html

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

复制代码
 1 /**
 2  * 基础的单例模式,Lazy模式,非线程安全
 3  * 优点:lazy,初次使用时实例化单例,避免资源浪费
 4  * 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题
 5  * 2、非线程安全。多线程下可能会有多个实例被初始化。
 6  * 
 7  * @author laichendong
 8  * @since 2011-12-5
 9  */
10 public class SingletonOne {
11     
12     /** 单例实例变量 */
13     private static SingletonOne instance = null;
14     
15     /**
16      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
17*/
18     private SingletonOne() {
19         
20     }
21     
22     /**
23      * 获取单例对象实例
24      * 
25      * @return 单例对象
26*/
27     public static SingletonOne getInstance() {
28         if (instance == null) { // 1
29             instance = new SingletonOne(); // 2
30         }
31         return instance;
32     }
33     
34 }
复制代码

  注释中已经有简单的分析了。接下来分析一下关于“非线程安全”的部分。

  1、当线程A进入到第28行(#1)时,检查instance是否为空,此时是空的。
  2、此时,线程B也进入到28行(#1)。切换到线程B执行。同样检查instance为空,于是往下执行29行(#2),创建了一个实例。接着返回了。
  3、在切换回线程A,由于之前检查到instance为空。所以也会执行29行(#2)创建实例。返回。
  4、至此,已经有两个实例被创建了,这不是我们所希望的。 

 怎么解决线程安全问题?

  方法一:同步方法。即在getInstance()方法上加上synchronized关键字。这时单例变成了  

复制代码
 1 /**
 2  * copyright © sf-express Inc
 3  */
 4 package com.something.singleton;
 5 
 6 /**
 7  * 同步方法 的单例模式,Lazy模式,线程安全
 8  * 优点:
 9  * 1、lazy,初次使用时实例化单例,避免资源浪费
10  * 2、线程安全
11  * 缺点:
12  * 1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题
13  * 2、每次调用getInstance()都要获得同步锁,性能消耗。
14  * 
15  * @author laichendong
16  * @since 2011-12-5
17  */
18 public class SingletonTwo {
19     
20     /** 单例实例变量 */
21     private static SingletonTwo instance = null;
22     
23     /**
24      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
25 */
26     private SingletonTwo() {
27         
28     }
29     
30     /**
31      * 获取单例对象实例
32      * 同步方法,实现线程互斥访问,保证线程安全。
33      * 
34      * @return 单例对象
35 */
36     public static synchronized SingletonTwo getInstance() {
37         if (instance == null) { // 1
38             instance = new SingletonTwo(); // 2
39         }
40         return instance;
41     }
42     
43 }
复制代码

 

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

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

复制代码
1     public static SingletonThree getInstance() {
2         if (instance == null) { // 1
3             synchronized (SingletonThree.class) {
4                 instance = new SingletonThree(); // 2
5             }
6         }
7         return instance;
8     }
复制代码

 

  这个方法可行么?分析一下发现是不行的!
  1、线程A和线程B同时进入//1的位置。这时instance是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、这时已经有两个实例创建了。 

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

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

 

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

  好吧,继续努力,解决由“无序写”带来的问题。

复制代码
 1     public static SingletonThree getInstance() {
 2         if (instance == null) { 
 3             synchronized (SingletonThree.class) {           // 1
 4                 SingletonThree temp = instance;             // 2
 5                 if (temp == null) {
 6                     synchronized (SingletonThree.class) {   // 3
 7                         temp = new SingletonThree();        // 4
 8                     }
 9                     instance = temp;                        // 5
10                 }
11             }
12         }
13         return instance;
14     }
复制代码

 

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

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

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

复制代码
 1 /**
 2  * 预先初始化static变量 的单例模式  非Lazy  线程安全
 3  * 优点:
 4  * 1、线程安全 
 5  * 缺点:
 6  * 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
 7  * 
 8  * @author laichendong
 9  * @since 2011-12-5
10  */
11 public class SingletonFour {
12     
13     /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */
14     private static SingletonFour instance = new SingletonFour();
15     
16     /**
17      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
18      */
19     private SingletonFour() {
20         
21     }
22     
23     /**
24      * 获取单例对象实例
25      * 
26      * @return 单例对象
27      */
28     public static SingletonFour getInstance() {
29         return instance;
30     }
31     
32 }
复制代码

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

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

  方法四:使用内部类。

 

复制代码
 1 /**
 2  * 基于内部类的单例模式  Lazy  线程安全
 3  * 优点:
 4  * 1、线程安全
 5  * 2、lazy
 6  * 缺点:
 7  * 1、待发现
 8  * 
 9  * @author laichendong
10  * @since 2011-12-5
11  */
12 public class SingletonFive {
13     
14     /**
15      * 内部类,用于实现lzay机制
16 */
17     private static class SingletonHolder{
18         /** 单例变量  */
19         private static SingletonFive instance = new SingletonFive();
20     }
21     
22     /**
23      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
24 */
25     private SingletonFive() {
26         
27     }
28     
29     /**
30      * 获取单例对象实例
31      * 
32      * @return 单例对象
33 */
34     public static SingletonFive getInstance() {
35         return SingletonHolder.instance;
36     }
37     
38 }
复制代码

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

 

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

分享到:
评论

相关推荐

    java单例模式实例

    在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们来看**懒汉式(Lazy Initialization)**。这种实现方式是在类被首次请求时才创建单例对象,延迟...

    Java 单例模式.pptx

    ### Java 单例模式详解 #### 一、什么是单例模式? 单例模式是一种常用的软件设计模式,在这种模式中,一个类只能拥有一个实例,并且该类必须自行创建并提供这个实例。通常,单例模式用于确保某个类在整个应用程序...

    Java 单例模式 工具类

    Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...

    Java 单例模式 懒汉模式

    Java 单例模式 懒汉模式 //懒汉式 多线程中不可以保证是一个对象

    Java SE程序 单例模式

    Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式...

    使用Java单例模式实现一个简单的日志记录器.txt

    ### 使用Java单例模式实现一个简单的日志记录器 #### 一、单例模式简介 单例模式是一种常用的软件设计模式,在该模式中,一个类只能创建一个实例,并且提供了一个全局访问点来访问该实例。单例模式的主要优点包括...

    Java单例模式设计

    Java单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象的场景中,或者当对象昂贵时(如数据库连接),能够节省系统资源,提高效率。本篇文章将深入探讨...

    JAVA单例模式的几种实现方法

    ### JAVA单例模式的几种实现方法 #### 一、饿汉式单例类 饿汉式单例类是在类初始化时就已经完成了实例化的操作。这种实现方式简单且线程安全,因为实例化过程是在编译期间完成的,不会受到多线程的影响。 **代码...

    Java单例模式应用研究.pdf

    ### Java单例模式应用研究 #### 一、单例模式概述 单例模式(Singleton Pattern)作为一种最基本的创建型设计模式,其主要目的是控制一个类的实例化过程,确保在整个应用程序中仅存在一个实例,并且该实例能够被全局...

    Java单例模式深入理解

    Java单例模式是一种设计模式,它允许在程序中创建唯一一个类实例,通常用于管理共享资源,例如数据库连接、线程池或者配置对象等。单例模式的核心在于限制类的构造函数,确保类只能被初始化一次,从而实现全局唯一的...

    java 获取 配置文件 属性 单例模式 高效加载

    通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包...

    实用Java的单例模式,实用于Java学习者

    实用Java的单例模式,实用于Java学习者 单例模式 单例模式

    Java实现多种单例模式

    在Java编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中尤其有用,因为它可以节省系统资源并确保对象间的协调一致。以下是...

    java单例模式及实现

    Java单例模式及实现 Java单例模式是一种常见的设计模式,确保某一个类只有一个实例,而且向这个系统提供这个实例。单例模式可以分为三种:懒汉式单例、饿汉式单例、登记式单例。 单例模式的要点 1. 某个类只能有...

    java单例模式连接数据库源码

    Java单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在数据库连接管理中,使用单例模式能有效控制资源,避免频繁创建和关闭数据库连接导致的性能损失和资源浪费。以下是对Java单例模式...

    java单例模式的例子

    Java单例模式是一种常见的设计模式,它在软件工程中用于控制类的实例化过程,确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统资源管理、缓存、日志记录等方面应用广泛。下面我们将深入探讨Java单例...

Global site tag (gtag.js) - Google Analytics