单例模式
单例模式的应用场景
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。
单例模式的常见写法
- 1、饿汉式单例
- 饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。
- 优点:
- 没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
- 缺点:
- 类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎。
- Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例
- 饿汉式单例写法:
public class HungrySingleton { private final static HungrySingleton hungrySingleton = new HungrySingleton(); // 或者: // private final static HungrySingleton hungrySingleton ; // static { // hungrySingleton = new HungrySingleton(); // } private HungrySingleton(){ } public static HungrySingleton getHungrySingleton() { return hungrySingleton; } }
- 2、懒汉式单例
- 懒汉式单例的特点是:被外部类调用的时候内部类才会加载
- 代码:
public class LazySingleton { private static LazySingleton lazySingleton = null; private LazySingleton(){ } public static LazySingleton getLazySingleton() { if(lazySingleton == null){ lazySingleton = new LazySingleton(); } return lazySingleton; } }
- 线程不安全,双重检查锁
public class LazySingleton { private volatile static LazySingleton lazySingleton = null; private LazySingleton(){ } public static LazySingleton getLazySingleton() { if(lazySingleton == null){ synchronized (LazySingleton.class){ if(lazySingleton == null){ lazySingleton = new LazySingleton(); //1.分配内存给这个对象 //2.初始化对象 //3.设置lazy指向刚分配的内存地址 //CPU内存重排序,有可能2和3颠倒,解决:加volatile关键字 } } } return lazySingleton; } }
- 内部类方式单例:
//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题 //完美地屏蔽了这两个缺点 public class LazyInnerClassSingleton { //默认使用LazyInnerClassGeneral的时候,会先初始化内部类 //如果没使用的话,内部类是不加载的 private LazyInnerClassSingleton() { } //每一个关键字都不是多余的 //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){ return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
- 静态内部类方式极端情况下单例会被破坏,如下代码
public class LazyInnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = LazyInnerClassSingleton.class; Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); Object o1 = declaredConstructor.newInstance(); Object o2 = declaredConstructor.newInstance(); System.out.println(o1 == o2);//false } }
- 可以在构造函数那里判断拦截一下
private LazyInnerClassSingleton() { if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许构建多个单例"); } }
- 变为:
//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题 //完美地屏蔽了这两个缺点 public class LazyInnerClassSingleton { //默认使用LazyInnerClassGeneral的时候,会先初始化内部类 //如果没使用的话,内部类是不加载的 private LazyInnerClassSingleton() { if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许构建多个单例"); } } //每一个关键字都不是多余的 //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){ return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
- 到此,还有更极端情况,序列化和反序列化方式也能破坏单例
- 代码如下:
public class SeriableSingleton implements Serializable{ private static final SeriableSingleton seriableSingleton= new SeriableSingleton(); private SeriableSingleton() { } public static SeriableSingleton getSeriableSingleton() { return seriableSingleton; } private String sayHello(){ return "hello"; } } //测试类: public class SeriableSingletonTest { public static void main(String[] args) { SeriableSingleton s1 = null; SeriableSingleton s2 = SeriableSingleton.getSeriableSingleton(); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SeriableSingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } }
- 解决办法:
- 重写
readResolve
- 变为:
import java.io.Serializable; //序列化就是说把内存中的状态通过转换成字节码的形式 //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO) //内存中状态给永久保存下来了 //反序列化 //讲已经持久化的字节码内容,转换为IO流 //通过IO流的读取,进而将读取的内容转换为Java对象 //在转换过程中会重新创建对象new public class SeriableSingleton implements Serializable{ private static final SeriableSingleton seriableSingleton= new SeriableSingleton(); private SeriableSingleton() { } public static SeriableSingleton getSeriableSingleton() { return seriableSingleton; } private Object readResolve(){ return seriableSingleton; } private String sayHello(){ return "hello"; } }
- 重写
- 3、注册式单例
注册式单例有两种写法:一种为容器缓存,一种为枚举登记
- 枚举式单例
- 代码:
public enum EnumSingleton implements Serializable{ INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; } private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } private String sayHello(){ return "hello Word"; } }
- 不重写
readResolve
情况下
public class EnumSingletonTest { public static void main(String[] args) { EnumSingleton s1 = null; EnumSingleton s2 = EnumSingleton.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (EnumSingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } } }
- 测试为true
- 用jad反编译工具可以看到枚举登记单例的代码:
发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对 象不可能被类加载器加载多次次。那么反射是否能破坏枚举式单例呢public final class EnumSingleton extends Enum implements Serializable { public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static EnumSingleton valueOf(String name) { return (EnumSingleton)Enum.valueOf(com/exercise/eg/patterns/singleton/register/EnumSingleton, name); } private EnumSingleton(String s, int i) { super(s, i); } public static EnumSingleton getInstance() { return INSTANCE; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } private String sayHello() { return "hello Word"; } public static final EnumSingleton INSTANCE; private Object data; private static final EnumSingleton $VALUES[]; static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); } }
运行结果:Cannot reflectively create enum objectspublic static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = EnumSingleton.class; Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,Integer.class); constructor.setAccessible(true); constructor.newInstance("Jim",666); }
- 看看 JDK 源码,进入 Constructor 的 newInstance()方法:
在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。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 volatile if (ca == null) { ca = acquireConstructorAccessor(); }@SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
- 枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现
- 注册式单例 容器缓存的写法
//Spring中的做法,就是用这种注册式单例 public class ContainerSingleton { private static Map<String,Object> iocMap = new ConcurrentHashMap<String,Object>(); public static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { synchronized (iocMap){ if(!iocMap.containsKey(className)){ Class<?> aClass = Class.forName(className); Object o = aClass.newInstance(); iocMap.put(className, o); return o; } return iocMap.get(className); } } } //测试: public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException { Object object = ContainerSingleton. getInstance("com.exercise.eg.patterns.singleton.register.Pojo"); System.out.println("end"); }
- 不重写
- 4、ThreadLocal单例
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> singletonThreadLocal =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getSingletonThreadLocal() {
return singletonThreadLocal.get();
}
private ThreadLocalSingleton() {
}
}
ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。