一般的单例模式
- public class Singleton {
- private static Singleton instance = null;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if(instance == null) {
- instance = new Singleton();
- }
- return instance;
- }
- }
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;
- }
- }
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;
- }
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 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() { }
- }
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");
- }
- }
enum Singleton {
INSTANCE;
public static Singleton getInstance() {
return INSTANCE;
}
public void sss() {
System.out.println("sss");
}
}
关于反射
以上的写法,除了枚举方式,其他的写法均可用反射得到Constructor来newInstance得到实例。
关于继承
无论是饿汉式,懒汉式写法均不可继承,因此有如下登记式写法。
- 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 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.";
- }
- }
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.";
}
}
ClassLoader对单例的影响
同样的单例类,由不同的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));
- }
- }
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
序列化对单例的影响
如果单例类实现了序列化接口,我们序列化一次,然后反序列化多次,会得到多个不同实例。我们通过实现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;
- }
- }