尊重版权:http://cantellow.iteye.com/blog/838473

第一种(懒汉。线程不安全):

Java代码  
  1. public class Singleton {
  2. private static Singleton instance;
  3. private Singleton (){}
  4. public static Singleton getInstance() {
  5. if (instance == null) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }

这样的写法lazy loading非常明显。可是致命的是在多线程不能正常工作。

另外一种(懒汉。线程安全):

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

这样的写法可以在多线程中非常好的工作,并且看起来它也具备非常好的lazy loading。可是。遗憾的是。效率非常低。99%情况下不须要同步。

第三种(饿汉):

Java代码  
  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. private Singleton (){}
  4. public static Singleton getInstance() {
  5. return instance;
  6. }
  7. }

这样的方式基于classloder机制避免了多线程的同步问题。只是。instance在类装载时就实例化,尽管导致类装载的原因有非常多种。在单例模式中大多数都是调用getInstance方法, 可是也不能确定有其它的方式(或者其它的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

第四种(饿汉,变种):

Java代码  
  1. public class Singleton {
  2. private Singleton instance = null;
  3. static {
  4. instance = new Singleton();
  5. }
  6. private Singleton (){}
  7. public static Singleton getInstance() {
  8. return this.instance;
  9. }
  10. }

表面上看起来区别挺大,事实上更第三种方式差点儿相同。都是在类初始化即实例化instance。

第五种(静态内部类):

Java代码  
  1. public class Singleton {
  2. private static class SingletonHolder {
  3. private static final Singleton INSTANCE = new Singleton();
  4. }
  5. private Singleton (){}
  6. public static final Singleton getInstance() {
  7. return SingletonHolder.INSTANCE;
  8. }
  9. }

这样的方式相同利用了classloder的机制来保证初始化instance时仅仅有一个线程。它跟第三种和第四种方式不同的是(非常细微的区别):第三种和第四种方式是仅仅要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果)。而这样的方式是Singleton类被装载了,instance不一定被初始化。由于SingletonHolder类没有被主动使用。仅仅有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

想象一下。假设实例化instance非常消耗资源,我想让他延迟载入,另外一方面。我不希望在Singleton类载入时就实例化,由于我不能确保Singleton类还可能在其它的地方被主动使用从而被载入,那么这个时候实例化instance显然是不合适的。

这个时候。这样的方式相比第三和第四种方式就显得非常合理。

第六种(枚举):

Java代码  
  1. public enum Singleton {
  2. INSTANCE;
  3. public void whateverMethod() {
  4. }
  5. }

这样的方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,并且还能防止反序列化又一次创建新的对象,可谓是非常坚强的壁垒啊,只是,个人觉得因为1.5中才增加enum特性,用这样的方式写不免让人感觉生疏。在实际工作中,我也非常少看见有人这么写过。

第七种(双重校验锁):

Java代码  
  1. public class Singleton {
  2. private volatile static Singleton singleton;
  3. private Singleton (){}
  4. public static Singleton getSingleton() {
  5. if (singleton == null) {
  6. synchronized (Singleton.class) {
  7. if (singleton == null) {
  8. singleton = new Singleton();
  9. }
  10. }
  11. }
  12. return singleton;
  13. }
  14. }

这个是另外一种方式的升级版,俗称双重检查锁定,具体介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

在JDK1.5之后,双重检查锁定才可以正常达到单例效果。

总结

有两个问题须要注意:

1.假设单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,比如一些servlet容器对每一个servlet使用全然不同的类装载器。这种话假设有两个servlet訪问一个单例类。它们就都会有各自的实例。

2.假设Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。无论如何。假设你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对第一个问题修复的办法是:

Java代码  
  1. private static Class getClass(String classname)
  2. throws ClassNotFoundException {
  3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  4. if(classLoader == null)
  5. classLoader = Singleton.class.getClassLoader();
  6. return (classLoader.loadClass(classname));
  7. }
  8. }

对第二个问题修复的办法是:

Java代码  
  1. public class Singleton implements java.io.Serializable {
  2. public static Singleton INSTANCE = new Singleton();
  3. protected Singleton() {
  4. }
  5. private Object readResolve() {
  6. return INSTANCE;
  7. }
  8. }

对我来说,我比較喜欢第三种和第五种方式,简单易懂,并且在JVM层实现了线程安全(假设不是多个类载入器环境),一般的情况下。我会使用第三种方式,仅仅有在要明白实现lazy loading效果时才会使用第五种方式,另外。假设涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,只是,我一直会保证我的程序是线程安全的,并且我永远不会使用第一种和另外一种方式,假设有其它特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。

========================================================================

superheizai同学总结的非常到位:

只是一般来说,第一种不算单例,第四种和第三种就是一种。假设算的话,第五种也能够分开写了。所以说,一般单例都是五种写法。

懒汉,恶汉。双重校验锁,枚举和静态内部类。

我非常高兴有这种读者。一起共勉。

【JAVA学习】单例模式的七种写法的更多相关文章

  1. Java:单例模式的七种写法

    第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton ( ...

  2. Java:单例模式的七种写法(转载)

    第一种(懒汉,线程不安全): package Singleton; /** * @echo 2013-10-10 懒汉 线程不安全 */ public class Singleton1 { priva ...

  3. Java:单例模式的七种写法[转]

    第一种(懒汉,线程不安全):  1 public class Singleton {   2     private static Singleton instance;   3     privat ...

  4. Java:单例模式的七种写法<转>

    第一种(懒汉,线程不安全):  1 public class Singleton {   2     private static Singleton instance;   3     privat ...

  5. 温故而知新(java实现)单例模式的七种写法

    第一种(懒汉,线程不安全): Java代码 public class Singleton { private static Singleton instance; private Singleton ...

  6. Java设计模式之单例模式(七种写法)

    Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...

  7. Java 单例模式的七种写法

    Java 单例模式的七种写法 第一种(懒汉,线程不安全) public class Singleton { private static Singleton instance; private Sin ...

  8. Android设计模式之单例模式的七种写法

    一 单例模式介绍及它的使用场景 单例模式是应用最广的模式,也是我最先知道的一种设计模式.在深入了解单例模式之前.每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式 ...

  9. KandQ:单例模式的七种写法及其相关问题解析

    设计模式中的单例模式可以有7种写法,这7种写法有各自的优点和缺点: 代码示例(java)及其分析如下: 一.懒汉式 public class Singleton { private static Si ...

随机推荐

  1. 【Android】ScrollView+GridView 显示问题

    在使用Android的ScrollView里面嵌套GridView时,设置android:layout_height="wrap_content"属性,运行界面的效果不会出现全部数 ...

  2. BZOJ 1060: [ZJOI2007]时态同步( 树形dp )

    坑爹...数据是错的..详见discuss  http://www.lydsy.com/JudgeOnline/wttl/wttl.php?pid=1060 先求根到叶子的距离最大值x, 然后把所有叶 ...

  3. C++获取当前时间和计算程序运行时间的方法

    C++获取当前时间和计算程序运行时间的方法 获取当前时间: #include <iostream> #include <Windows.h> using namespace s ...

  4. CodeForces 276D – Little Girl and Maximum XOR 贪心

    整整10个月后第二次搞这个问题才搞懂........第一次还是太随意了. 解题思路: 经过打表可得规律答案要么是0 要么是2的N次 - 1 要得到最大的XOR值,其值一定是2的N次 - 1 即在 l ...

  5. 关于怎么C#控制台窗口中怎么创建连接查询数据库操作

    首先需要新建一张表,为了测试随建了一张学生表 新建号一张表之后就可以对数据库进行操作了 列举了常用的增删改查 操作 static void Main(string[] args)        { s ...

  6. 数据库基础学习3-T-SQL语句

    一.语句操作的基本方法 1.选中执行. 2.注释的方法‘--’. 二.数据类型 整数:int,bigint,smallint 小数:float,decimal(长度,精度) 字符:char(n),va ...

  7. 再写KMP算法

    #include<iostream> #include<string> using namespace std; void getNext(char const*T,int l ...

  8. Java 多线程-生产者、消费者

    一.整体代码 ThreadDemo.java public class ThreadDemo { public static void main(String[] args) { Godown god ...

  9. 怎样实现cocos2d-x之文字渲染

    // 1.创建一段文本 // create函数的三个参数分别为:文本内容.字体和字体大小 CCLabelTTF *font=CCLabelTTF::create("Hello World&q ...

  10. EasyUi 中datagrid 实现查询方法

    1.在初始化表格方法中添加传入參数,例如以下: //初始化表格 function initTable(<strong><span style="color:#ff6666; ...