静态内部类的方式
public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return InnerClassSingleton.singleton; } private class InnerClassSingleton{ protected static Singleton singleton = new Singleton(); } } |
然而,虽然静态内部类模式可以很好地避免并发创建出多个实例的问题,但这种方式仍然有其存在的隐患。
存在的隐患
· 一旦一个实例被持久化后重新生成的实例仍然有可能是不唯一的。
· 由于java提供了反射机制,通过反射机制仍然有可能生成多个实例。
序列化和反序列化带来的问题:反序列化后两个实例不一致了。
private static void singleSerializable() { try (FileOutputStream fileOutputStream=new FileOutputStream(new File("myObjectFilee.txt")); ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);) { // SingletonObject singletonObject = SingletonObject.getInstance(); // InnerClassSingleton singletonObject = InnerClassSingleton.getInstance(); EnumSingleton singletonObject = EnumSingleton.INSTANCE; objectOutputStream.writeObject(singletonObject); objectOutputStream.close(); fileOutputStream.close(); System.out.println(singletonObject.hashCode()); } catch (IOException e) { e.printStackTrace(); } try (FileInputStream fileInputStream=new FileInputStream(new File("myObjectFilee.txt")); ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);) { // SingletonObject singleTest=(SingletonObject) objectInputStream.readObject(); // InnerClassSingleton singleTest=(InnerClassSingleton) objectInputStream.readObject(); EnumSingleton singleTest=(EnumSingleton) objectInputStream.readObject(); objectInputStream.close(); fileInputStream.close(); System.out.println(singleTest.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } |
问题点及解决办法
ObjectInputStream中的readOrdinaryObject。 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } } |
调用自定义的readResolve方法
protected Object readResolve(){
System.out.println("调用了readResolve方法!");
return InnerClassSingleton.getInstance();
}
通过反射机制获取到两个不同的实例
private static void attack() { try { Class<?> classType = InnerClassSingleton.class; Constructor<?> constructor = classType.getDeclaredConstructor(null); constructor.setAccessible(true); InnerClassSingleton singleton = (InnerClassSingleton) constructor.newInstance(); InnerClassSingleton singleton2 = InnerClassSingleton.getInstance(); System.out.println(singleton == singleton2); //false } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } |
解决方案 : 私有构造方法中进行添加标志判断。
private InnerClassSingleton() {
synchronized (InnerClassSingleton.class) {
if (false == flag) {
flag = !flag;
} else {
throw new RuntimeException("单例模式正在被攻击");
}
}
}
单例最优方案,枚举的方式
枚举实现单例的优势
· 自由序列化;
· 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
· 线程安全;
public enum Singleton {
INSTANCE;
private Singleton(){}
}
Hibernate的解决方案
通过ThreadLocal的方式
import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.cfg.Configuration; public class HibernateSessionFactory { private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml"; private static final ThreadLocal threadLocal = new ThreadLocal(); private static Configuration configuration = new Configuration(); private static org.hibernate.SessionFactory sessionFactory; private static String configFile = CONFIG_FILE_LOCATION; static { try { configuration.configure(configFile); sessionFactory = configuration.buildSessionFactory(); } catch (Exception e) { System.err.println("%%%% Error Creating SessionFactory %%%%"); e.printStackTrace(); } } private HibernateSessionFactory() { } public static Session getSession() throws HibernateException { Session session = (Session) threadLocal.get(); if (session == null || !session.isOpen()) { if (sessionFactory == null) { rebuildSessionFactory(); } session = (sessionFactory != null) ? essionFactory.openSession() : null; threadLocal.set(session); } return session; } // Other methods... } |