常见的单例模式有两种:饿汉式;懒汉式。
饿汉式代码如下:
/**
* 单例模式之饿汉式
* @author leon
* @time 2018年4月27日 下午2:28:14
*/
public class HungrySingleton {
// 三要素
// 1.私有对象
private static HungrySingleton hungrySingleton = new HungrySingleton();
// 2.私有构造器
private HungrySingleton() {}
// 3.返回实例的静态方法
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
饿汉式的实例对象是在类初始化时就生成的,而类的初始化时线程安全的,因此饿汉式也是线程安全的。不过,其对象如果特别庞大,类似于MyBatis的Configuration,应用的初始化速度会相对较慢,即没有延迟加载的特性。
懒汉式代码如下:
/**
* 2. 懒汉式单例模式
* @author leon
* @time 2018年4月12日 上午6:54:45
*/
public class LazySingleton{
// 三要素
// 1.私有对象
private static final LazySingleton LAZY_SINGLETON;
// 2.私有构造器
private LazySingleton() throws Exception {}
// 3.返回实例的静态方法
public static synchronized LazySingleton getInstance() throws Exception {
if(LAZY_SINGLETON == null) {
LAZY_SINGLETON = new LazySingleton();
}
return LAZY_SINGLETON;
}
}
懒汉式的实例对象在类初始化的时候并没有生成,而是在返回实例的同步静态方法中生成的,实现了延迟加载的特性。由于java字节码重排和锁的持有和释放的问题,可能会造成对象重复生成的问题,因此非线程安全。
而反射和序列化会生成不一样的对象,下面就如何规避这两种情况做一下解决方案的分析。
首先是反射,反射生成class对象方式为三种:Class.forName(“”);obj.class;Class.class。通过反射我们可以获取class对象,进而获取构造器,通过将构造器的属性转换为公有,进而通过构造器生成实例对象。代码如下:
LazySingleton lazySingleton1 = LazySingleton.getInstance();
LazySingleton lazySingleton2 = LazySingleton.getInstance();
// 并没有重写hashCode和equals方法,因此下面比较的是对象引用的内存地址
System.out.println(lazySingleton1 == lazySingleton2);// true
Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton3 = constructor.newInstance();
System.out.println(lazySingleton2 == lazySingleton3);// false
面对此问题,还没有好的解决方法,解决方法就是在私有构造器里抛出重复生成对象的异常,代码如下:
private LazySingleton() throws Exception {
// 重复生成对象的时候,抛出异常,防止通过反射生成
if(lazySingleton != null) {
throw new Exception("LazySingleton 是单例对象,不能重复生成");
}
}
其次是序列化,首先肯定LazySingleton要实现序列化接口Serializable,通过下面的代码测试可知,反序列化生成的是不一样的实例对象。测试代码如下:
testObjectOutputStream(lazySingleton2, new File("e:\\obj.txt"));
LazySingleton lazySingleton4 = testObjectInputStream(new File("e:\\obj.txt"));
System.out.println(lazySingleton2 == lazySingleton4);//false
// 反序列化
@SuppressWarnings("unchecked")
public static <T> T testObjectInputStream(File file) {// 读
try(InputStream in = new FileInputStream(file);ObjectInputStream objectInputStream = new ObjectInputStream(in);){
return (T) objectInputStream.readObject();
}catch(Exception e){
return null;
}
}
// 序列化
public static boolean testObjectOutputStream(Object obj, File file) {// 写
try(OutputStream out = new FileOutputStream(file);ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);){
objectOutputStream.writeObject(obj);
}catch(Exception e){
return false;
}
return true;
}
怎么解决反序列的问题,就是在类的内部添加如下方法:
// 防止通过反序列化的方式生成重复对象
private Object readResolve() throws ObjectStreamException {
return lazySingleton;
}
测试结果确实生成了同样的对象,测试代码和之前一样结果如下:
System.out.println(lazySingleton2 == lazySingleton4);//true
为什么能够通过添加此代码解决此问题呢?看源码可知在readResolve方法被实现的时候,序列化的时候是通过反射调用的此方法返回的实例对象(具体请参看ObjectInputStream和ObjectStreamClass类)。
还有另外三种:双检查锁式;枚举式;静态内部类式,在后面的博文中再论述。