当前位置: 代码迷 >> 综合 >> 注册式单例
  详细解决方案

注册式单例

热度:1   发布时间:2024-02-28 15:56:08.0

将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleton getInstance(){return INSTANCE;}
}
public class EnumSingletonTest {
//    public static void main(String[] args) {
//        try {
//            EnumSingleton instance1 = null;
//
//            EnumSingleton instance2 = EnumSingleton.getInstance();
//            instance2.setData(new Object());
//
//            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
//            ObjectOutputStream oos = new ObjectOutputStream(fos);
//            oos.writeObject(instance2);
//            oos.flush();
//            oos.close();
//
//            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
//            ObjectInputStream ois = new ObjectInputStream(fis);
//            instance1 = (EnumSingleton) ois.readObject();
//            ois.close();
//
//            System.out.println(instance1.getData());
//            System.out.println(instance2.getData());
//            System.out.println(instance1.getData() == instance2.getData());
//
//        }catch (Exception e){
//            e.printStackTrace();
//        }
//    }public static void main(String[] args) {try {Class clazz = EnumSingleton.class;Constructor c = clazz.getDeclaredConstructor(String.class,int.class);c.setAccessible(true);EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666);}catch (Exception e){e.printStackTrace();}}}
static
{INSTANCE = new EnumSingleton("INSTANCE", 0);$VALUES = (new EnumSingleton[] {INSTANCE});
}

原来,枚举式单例在静态代码块中就给INSTANCE 进行了赋值,是饿汉式单例的实现。至此,我们还可以试想,序列化我们能否破坏枚举式单例呢?我们不妨再来看一下JDK源码,还是回到ObjectInputStream 的readObject0()方法:

private Object readObject0(boolean unshared) throws IOException {...case TC_ENUM:return checkResolve(readEnum(unshared));...
}

我们看到在readObject0()中调用了readEnum()方法,来看readEnum()中代码实现:

case TC_ENUM:return checkResolve(readEnum(unshared));
private Enum<?> readEnum(boolean unshared) throws IOException {if (bin.readByte() != TC_ENUM) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);if (!desc.isEnum()) {throw new InvalidClassException("non-enum class: " + desc);}int enumHandle = handles.assign(unshared ? unsharedMarker : null);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(enumHandle, resolveEx);}String name = readString(false);Enum<?> result = null;Class<?> cl = desc.forClass();if (cl != null) {try {@SuppressWarnings("unchecked")Enum<?> en = Enum.valueOf((Class)cl, name);result = en;} catch (IllegalArgumentException ex) {throw (IOException) new InvalidObjectException("enum constant " + name + " does not exist in " +cl).initCause(ex);}if (!unshared) {handles.setObject(enumHandle, result);}}handles.finish(enumHandle);passHandle = enumHandle;return result;
}

我们发现枚举类型其实通过类名和Class 对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。那么反射是否能破坏枚举式单例呢?来看一段测试代码:

Enum<?> en = Enum.valueOf((Class)cl, name);
public static void main(String[] args) {try {Class clazz = EnumSingleton.class;Constructor c = clazz.getDeclaredConstructor(String.class,int.class);c.setAccessible(true);EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666);}catch (Exception e){e.printStackTrace();}
}

报的是java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这时候,我们打开java.lang.Enum 的源码代码,查看它的构造方法,只有一个protected的构造方法,代码如下:

protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;
}

这时错误已经非常明显了,告诉我们Cannot reflectively create enum objects,不能用反射来创建枚举类型。还是习惯性地想来看看JDK 源码,进入Constructor 的newInstance()方法:

@CallerSensitive
public T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException
{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor;   // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;
}

在newInstance()方法中做了强制性的判断,如果修饰符是Modifier.ENUM 枚举类型,直接抛出异常。到这为止,我们是不是已经非常清晰明了呢?枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。
接下来看注册式单例还有另一种写法,容器缓存的写法,创建ContainerSingleton 类:

//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {private ContainerSingleton(){}private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();public static Object getInstance(String className){synchronized (ioc) {if (!ioc.containsKey(className)) {Object obj = null;try {obj = Class.forName(className).newInstance();ioc.put(className, obj);} catch (Exception e) {e.printStackTrace();}return obj;} else {return ioc.get(className);}}}
}

容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。到此,注册式单例介绍完毕。我们还可以来看看Spring 中的容器式单例的实现代码:

public class ContainerSingletonTest {public static void main(String[] args) {try {long start = System.currentTimeMillis();ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() {public void handler() {Object obj = ContainerSingleton.getInstance("com.leon.pattern.singleton.Pojo");;System.out.println(System.currentTimeMillis() + ": " + obj);}}, 10,6);long end = System.currentTimeMillis();System.out.println("总耗时:" + (end - start) + " ms.");}catch (Exception e){e.printStackTrace();}}
}
public class Pojo {
}
public class ConcurrentExecutor {/*** @param runHandler* @param executeCount 发起请求总数* @param concurrentCount 同时并发执行的线程数* @throws Exception*/public static void execute(final RunHandler runHandler,int executeCount,int concurrentCount) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();//控制信号量,此处用于控制并发的线程数final Semaphore semaphore = new Semaphore(concurrentCount);//闭锁,可实现计数量递减final CountDownLatch countDownLatch = new CountDownLatch(executeCount);for (int i = 0; i < executeCount; i ++){executorService.execute(new Runnable() {public void run() {try{//执行此方法用于获取执行许可,当总计未释放的许可数不超过executeCount时,//则允许同性,否则线程阻塞等待,知道获取到许可semaphore.acquire();runHandler.handler();//释放许可semaphore.release();}catch (Exception e){e.printStackTrace();}countDownLatch.countDown();}});}countDownLatch.await();//线程阻塞,知道闭锁值为0时,阻塞才释放,继续往下执行executorService.shutdown();}public interface RunHandler{void handler();}
}

 

  相关解决方案