一般的单例模式
此方式已知问题:
1.如果由不同的ClassLoader去load,有可能存在多个实例。
2.线程不安全。假设线程1进入if判断,正在创建对象的时候,线程2进入,判断成功,这样就会创建2个对象实例。
改进方式,同步化getInstance方法,也就是懒汉式写法。
但getInstance会被外部线程比较频繁的调用,同步比非同步的性能消耗要昂贵很多,因此这样的做法会存在很大的额外性能消耗。因此产生另外一种改进方式,双重检查写法。
此写法存在的问题在于singleton = new Singleton() 这句代码对于编译器来说,是分两步进行。首先初始化一个singleton对象并随意赋一个值,然后调用Singleton类的构造器赋值。因此在第一步完成后singleton == null这句判断已经不成立了,但此时singleton只是一个临时值。如果线程2此时进入getInstance,就会把这个singleton给返回出去。由于java的内存模型,双重检查并不能成功的起到作用。
采用饿汉式的写法也可避免线程安全问题.但是任何对Singleton类的访问(内部的static final变量除外,因为jvm会把它们直接编译为常量),比如另外一个static方法被访问,会引起jvm去初始化instance,而此时我们的本意是不想加载单例类的。同时因为没有延迟加载,最明显的缺点就是如果构造器内的方法比较耗时,则加载过程会比较长。对于一般的应用,构造方法内的代码不涉及到读取配置、远程调用、初始化IOC容器等长时间执行的情况,用这种方式是最简单的。
如果我们既想使用延迟加载的好处,让类在被使用的时候才去加载,又想避免额外的同步调用开销,同时还不使用双重检查的模式,可以用初始化一个中间的容器类来解决这个问题。
就可以用如下的写法:
java中,只有一个类被用到的时候才被初始化。在getInstance方法被调用的时候,如果SingletonHolder类没有被加载,就会去加载,起到延迟加载的作用,同时也能保持多线程下的语义正确性。
如果是jdk1.5以上,还可采用enum方式来实现,也可避免多线程的问题。
[color=red]关于反射[/color]
以上的写法,除了枚举方式,其他的写法均可用反射得到Constructor来newInstance得到实例。
[color=red]关于继承[/color]
无论是饿汉式,懒汉式写法均不可继承,因此有如下登记式写法。
对于子类的
[color=red]ClassLoader对单例的影响[/color]
同样的单例类,由不同的ClassLoader装载就会有多个实例,为了确保我们的单例类只会被同个类ClassLoader加载,我们就需要为它指定一个ClassLoader
[color=red]序列化对单例的影响[/color]
如果单例类实现了序列化接口,我们序列化一次,然后反序列化多次,会得到多个不同实例。我们通过实现readResolve()来解决这个问题。
[color=red]JavaAPI中有哪些用到了单例模式[/color]
Runtime类
它里面用到的是饿汉式写法。
[color=red][size=large][b]Note:[/b][/size][/color]网上查到不少文章说Calendar类是singleton模式的应用,进入JDK1.5的sourcecode,发现并不是这样,纯粹是谬误。
很简单的代码就可以验证:
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);