基于DCL的单例模式的分析

        最近在看三石-道的关于并发的一些博客,在一篇博客中看到了基于DCL实现的单例模式:

              http://www.molotang.com/articles/407.html

        于是在并发编程网上又看了两篇博客,以加深对单例模式的理解。

        http://ifeve.com/doublecheckedlocking/

        http://ifeve.com/syn-jmm-pre/

        首先总结一下实现单例模式的几种办法:

 

1:最简单,预先初始化了单例的对象,有一定的内存消耗:

public class Singleton { 
private static final Singleton INSTANCE = new Singleton(); 
private Singleton() { 
} 
public static Singleton getInstance() { 
return INSTANCE; 
} 
}

 

 2:比较常见的,加同步锁:

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

 

3:DCL式(double checking lock),为了避免取得过期对象而想出来的:

public class Singleton { 
private static volatile Singleton instance = null; 
private Singleton() { 
} 
public static Singleton instance() {
if (instance == null) {
synchronized (Singleton.class) { 
if (instance == null) {
instance = new Singleton(); 
} 
} 
} 
return instance; 
} 
}

    DCL式的会有很多问题,在《Java并发编程实践》一书中的16.2.4一节对其进行了说明:问题可能出来线程可能看到部分创建的对象(存在重排的可能性),打算有时间了写个例子验证一下。

 

4:解决了DCL的缺陷,由Bill Pugh提出:不仅实现了延迟加载而且可以实现同步:

public class Singleton { 
private Singleton() { 
} 
private static class SingletonHolder { 
public static final Singleton INSTANCE = new Singleton(); 
} 
public static Singleton getInstance() { 
return SingletonHolder.INSTANCE; 
} 
}

 

 5:《Effective Java》上推荐的,采用enum:

public enum Singleton { 
  INSTANCE; 
  void show(){System.out.println("Singleton");}
}
 
//客户端调用:
public class Test {
	public static void main(String args[]){
		Singleton.INSTANCE.show();
	}
}

 

      enum和class不同,enum的成员函数和成员变量默认都是public的,而class默认都是private的。

 

6:还可以用线程局部存储来修复DCL,每个线程各自保存一个flag来表示该线程是否执行了同步。

class Foo {
 /** If perThreadInstance.get() returns a non-null value, this thread
	has done synchronization needed to see initialization
	of helper */
	 private final ThreadLocal perThreadInstance = new ThreadLocal();
	 private Helper helper = null;
	 public Helper getHelper() {
		 if (perThreadInstance.get() == null) createHelper();
		 return helper;
	 }
	 private final void createHelper() {
		 synchronized(this) {
			 if (helper == null)
				 helper = new Helper();
		 }
	 // Any non-null value would do as the argument here
		 perThreadInstance.set(perThreadInstance);
	 }
}

   

      但是,如果我们采用序列化/烦序列化的时候,单例可能会产生多个对象。这样单例就失败了。

      当然了,采用enum实现单例的话是不用担心序列化/反序列化的,因为readObject和writeObject两个Serializable接口中的方法对enum是单独处理的。

      如果没有采用enum实现单例,又遇到这个类实现了Seriaalizable接口需要序列化/反序列化的情况也不要担心,只要重写readResolve方法就可以了,在readResolve方法中返回之前对象的引用。

像这样:

public class Singleton implements Serializable { 
private static final Singleton INSTANCE = new Singleton(); 
private Singleton() { 
} 
private Object readResolve() { 
//这个很重要
return INSTANCE; 
} 
public static Singleton getInstance() { 
return INSTANCE; 
} 
}

   当进行反序列化时,  ois.readObject();时内部就是通过反射检查implements Serializable的类有没有readResolve方法,如果有就把readResolve的返回值作为ois.readObject();的返回值. 所以readResolve必须返回之前对象的引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值