饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
注意:必须是私有构造方法,防止不会被其他代码实例化。这种实现唯一的不足是不能实现延迟加载。
懒汉式-不同步
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) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意:这种实现考虑了多线程的情况,但是由于做了同步,会导致性能较差(相对于饿汉式)。
懒汉式改造
public class StaticSingleton {
private StaticSingleton() {}
private static class SingletonHolder {
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.instance;
}
}
在这种实现中,使用内部类来维护单例的实例。当StaticSingleton被实例化时,其内部类并不会实例化。当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。
同时,由于实例的建立,是在类加载时完成的,所以天生对多线程友好。getInstance()方法也不需要同步关键字。
因此,这种实现既做到了延迟加载,又不用使用同步关键字。
使用枚举
/**
* @author j.tommy
* @version 1.0
* @date 2018/8/7
*/
public class SingleObject {
private SingleObject() {
}
private enum SingletonInstance {
INSTANCE;
private SingleObject instance;
//JVM会保证此方法绝对只调用一次
SingletonInstance() {
instance = new SingleObject();
}
public SingleObject getInstance() {
return instance;
}
}
public static SingleObject getInstance() {
return SingletonInstance.INSTANCE.getInstance();
}
public static void main(String[] args) {
// 测试100个线程获取单例实例对象是否是同一个。
IntStream.rangeClosed(1,100).forEach(i -> new Thread("t-"+i){
public void run() {
System.out.println(Thread.currentThread().getName() + "==>" + SingleObject.getInstance());
}
}.start());
}
}
确保反序列化仍然得到单例对象
通常,使用上面的方式创建的单例已经能确保是唯一的实例。但仍然有例外情况生成多个实例。比如,通过反射机制,强行调用类的私有构造方法。或者对象序列化/反序列化。
实现序列化接口的单例类:
public class SerSingleton implements Serializable {
private static SerSingleton instance = new SerSingleton();
private SerSingleton() {}
public static SerSingleton getInstance() {
return instance;
}
}
测试
SerSingleton s1 = SerSingleton.getInstance();
// 将单例对象串行化到文件
String filepath = "d:/SerSingleton.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filepath));
oos.writeObject(s1);
oos.flush();
oos.close();
System.out.println(s1);
// 从文件读出原有的单例对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filepath));
SerSingleton s2 = (SerSingleton) ois.readObject();
ois.close();
System.out.println(s2);
System.out.println(s1 == s2);
测试结果:
可以看到,经过反序列化后产生了不同的实例。
我们现在对SerSinglton增加一个readResolve()方法:
public class SerSingleton implements Serializable {
private static SerSingleton instance = new SerSingleton();
private SerSingleton() {}
public static SerSingleton getInstance() {
return instance;
}
private Object readResolve() { // 阻止生成新的实例,总是返回当前对象。
return instance;
}
}
再次测试:
可以看到,经过反序列化后得到的仍然是同一个实例对象。
事实上,在实现了私有的readResolve()方法后,readObject已经形同虚设,它直接使用了readResolve()替换了原本的返回值,从而在形式上构造了单例。
实际使用建议使用静态内部类或枚举的方式,既能保证延迟初始化,又是线程安全的。
参考:《Java程序性能优化-让你的Java程序更快、更稳定》(葛一宁等编著)》