Java中的Singleton pattern

一般的单例模式
public class Singleton { 
private static Singleton instance = null;

private Singleton() {
}

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

此方式已知问题:
1.如果由不同的ClassLoader去load,有可能存在多个实例。
2.线程不安全。假设线程1进入if判断,正在创建对象的时候,线程2进入,判断成功,这样就会创建2个对象实例。

改进方式,同步化getInstance方法,也就是懒汉式写法。
public class Singleton { 
private static Singleton instance = null;

private Singleton() {
}

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

但getInstance会被外部线程比较频繁的调用,同步比非同步的性能消耗要昂贵很多,因此这样的做法会存在很大的额外性能消耗。因此产生另外一种改进方式,双重检查写法。
public static Singleton getInstance() { 
if(singleton == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}

此写法存在的问题在于singleton = new Singleton() 这句代码对于编译器来说,是分两步进行。首先初始化一个singleton对象并随意赋一个值,然后调用Singleton类的构造器赋值。因此在第一步完成后singleton == null这句判断已经不成立了,但此时singleton只是一个临时值。如果线程2此时进入getInstance,就会把这个singleton给返回出去。由于java的内存模型,双重检查并不能成功的起到作用。


采用饿汉式的写法也可避免线程安全问题.但是任何对Singleton类的访问(内部的static final变量除外,因为jvm会把它们直接编译为常量),比如另外一个static方法被访问,会引起jvm去初始化instance,而此时我们的本意是不想加载单例类的。同时因为没有延迟加载,最明显的缺点就是如果构造器内的方法比较耗时,则加载过程会比较长。对于一般的应用,构造方法内的代码不涉及到读取配置、远程调用、初始化IOC容器等长时间执行的情况,用这种方式是最简单的。
public class Singleton { 
private static Singleton instance = new Singleton();

private Singleton() {
}

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


如果我们既想使用延迟加载的好处,让类在被使用的时候才去加载,又想避免额外的同步调用开销,同时还不使用双重检查的模式,可以用初始化一个中间的容器类来解决这个问题。
就可以用如下的写法:
public class Singleton {  
private static class SingletonHolder {
static Singleton instance = new Singleton();
}

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

private Singleton() { }

}

java中,只有一个类被用到的时候才被初始化。在getInstance方法被调用的时候,如果SingletonHolder类没有被加载,就会去加载,起到延迟加载的作用,同时也能保持多线程下的语义正确性。

如果是jdk1.5以上,还可采用enum方式来实现,也可避免多线程的问题。
enum Singleton {   
INSTANCE;
public static Singleton getInstance() {
return INSTANCE;
}

public void sss() {
System.out.println("sss");
}
}


[color=red]关于反射[/color]
以上的写法,除了枚举方式,其他的写法均可用反射得到Constructor来newInstance得到实例。

[color=red]关于继承[/color]
无论是饿汉式,懒汉式写法均不可继承,因此有如下登记式写法。
public class Sington {
private static HashMap<String, Sington> map = new HashMap<String, Sington>();

protected Sington() {
}

public static synchronized Sington getInstance(String classname) {
Sington singleton = (Sington) map.get(classname);
if (singleton != null) {
return singleton;
}
try {
singleton = (Sington) Class.forName(classname).newInstance();
} catch (ClassNotFoundException cnf) {

} catch (InstantiationException ie) {

} catch (IllegalAccessException ia) {

}
map.put(classname, singleton);
return singleton;
}
}

对于子类的
public class SingtonChild extends Sington {
public SingtonChild() {
}

static public SingtonChild getInstance() {
return (SingtonChild) Sington
.getInstance("util.SingtonChild");
}

public String about() {
return "Hello,I am children.";
}
}


[color=red]ClassLoader对单例的影响[/color]
同样的单例类,由不同的ClassLoader装载就会有多个实例,为了确保我们的单例类只会被同个类ClassLoader加载,我们就需要为它指定一个ClassLoader
private static Class getClass(String classname)  
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}


[color=red]序列化对单例的影响[/color]
如果单例类实现了序列化接口,我们序列化一次,然后反序列化多次,会得到多个不同实例。我们通过实现readResolve()来解决这个问题。
public class Singleton implements Serializable { 
private static Singleton instance = null;

private Singleton() { }

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

private Object readResolve() {
return instance;
}
}


[color=red]JavaAPI中有哪些用到了单例模式[/color]
Runtime类
Runtime.getRuntime();

它里面用到的是饿汉式写法。
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}


[color=red][size=large][b]Note:[/b][/size][/color]网上查到不少文章说Calendar类是singleton模式的应用,进入JDK1.5的sourcecode,发现并不是这样,纯粹是谬误。
很简单的代码就可以验证:
	Calendar c1 = Calendar.getInstance();
Calendar c2 = Calendar.getInstance();
Runtime runtime1 = Runtime.getRuntime();
Runtime runtime2 = Runtime.getRuntime();
System.out.println(c1 == c2);
System.out.println(runtime1 == runtime2);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值