1、概念
创建型模式
确保一个类在任何情况下都绝对只有一个实例,并且提供一个全局访问点。
2、模式
1)饿汉式单例模式
在类加载的时候就立即初始化,并且创建单例对象。
绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题
//标准代码
public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return hungrySingleton;}
}//静态代码块
public class HungryStaticSingleton {private static final HungryStaticSingleton hungrySingleton;static {hungrySingleton = new HungryStaticSingleton();}private HungryStaticSingleton() {}public static HungryStaticSingleton getInstance() {return hungrySingleton;}
}
适用:
单例对象较少的情况,可以保证线程安全,执行效率高
缺点:
所有对象类加载的时候就实例化,如果系统中有大批量的单例对象存在,那么系统初始化导致大量的内存浪费
2)懒汉式单例
单例对象要在被使用的时候才会初始化
public class LazySimpleSingleton {private LazySimpleSingleton() {}private static LazySimpleSingleton lazy = null;public static LazySimpleSingleton getInstance() {if(lazy == null){lazy = new LazySimpleSingleton();}return lazy;}}
导致一个新的问题,如果在多线程环境下,会出现线程安全问题
public class ExectorThread implements Runnable {@Overridepublic void run() {LazySimpleSingleton instance = LazySimpleSingleton.getInstance();System.out.println(Thread.currentThread().getName() + ":" + instance);}
}public class Test {public static void main(String[] args) {Thread thread = new Thread(new ExectorThread());Thread thread1 = new Thread(new ExectorThread());thread.start();thread1.start();System.out.println("END");}
}
DEBUG多线程环境下,可以看到LazySimpleSingleton被实例化了两次,可能会出现打印结果一致的情况,实际也是后面执行的线程覆盖了前者的结果。
public class LazySingleton {private LazySingleton() {}private static LazySingleton lazy = null;public synchronized static LazySingleton getInstance() {if(lazy == null){lazy = new LazySingleton();}return lazy;}}
加上synchronized关键字加锁,如果在线程数量较多的情况下,如果CPU分配压力上升,会导致大批线程阻塞,导致程序性能下降。
public class LazyDoubleCheckSingleton {private volatile static LazyDoubleCheckSingleton lazy = null;private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {if (lazy == null) {synchronized (LazyDoubleCheckSingleton.class) {if (lazy == null) {lazy = new LazyDoubleCheckSingleton();}}}return lazy;}}
第一个线程调用getInstance()时,第二个线程也可以调用。当一个线程执行 synchronized 时会上锁,第二个线程会变成MONITOR状态,出现阻塞,阻塞并不是基于整个LazyDoubleCheckSingleton类的上锁,而是在getInstance()内部的阻塞,只要逻辑不是很负责,调用者感受不到阻塞的时间差。
3)注册式单例模式(登记式单例模式
将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
public enum EnumSingleton {INSTANCE;private Object data;public void setData(Object data) {this.data = data;}public Object getData() {return data;}public static EnumSingleton getInstance(){return INSTANCE;}
}
public static void main(String[] args) {try {EnumSingleton instance = EnumSingleton.getInstance();instance.setData(new Object());FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(instance);oos.flush();oos.close();FileInputStream fis = new FileInputStream("EnumSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);EnumSingleton enumSingleton = null;enumSingleton = (EnumSingleton) ois.readObject();ois.close();System.out.println(instance.getData());System.out.println(enumSingleton.getData());System.out.println(enumSingleton.getData() == instance.getData());} catch (Exception e) {e.printStackTrace();}}
枚举式单例模式在静态代码块中就给INSTANCE进行赋值,是饿汉式单例模式的体现。
看源代码:
- readEnum():,通过类名和类对象类找到一个唯一的枚举对象。枚举对象不可能被类加载器加载多次。
再看反射是否能破坏单例:
try{Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;Constructor c = enumSingletonClass.getDeclaredConstructor();c.newInstance();}catch (Exception e){e.printStackTrace();}
没有找到无参的构造方法
修改代码
不能用发射来创建枚举类型
在源码中,newInstance()做了强制性的判断
4)容器式单例
public class ContainerSingleton {private ContainerSingleton() {}private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();public static Object getBean(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);}}}
}
适用于需要大量创建单例对象的场景,便于管理。但是是非线程安全的。
5)线程单例
public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =new ThreadLocal<ThreadLocalSingleton>() {@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton(){}public static ThreadLocalSingleton getInstance(){return threadLocalInstance.get();}
}
public class test {public static void main(String[] args) {System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());Thread thread = new Thread(new ExectorThread());Thread thread1 = new Thread(new ExectorThread());thread.start();thread1.start();}
}
主线程无论调用多少次,获取到的实例是同一个,都在连个子线程中分别获取到了不同的实例。
ThreadLocal将所有的对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上以空间换时间来实现线程隔离。
3、破坏单例
1)反射破坏单例
try {Class<?> clazz = LazyInnerSingleton.class;Constructor constructor = clazz.getDeclaredConstructor(null);//强制访问constructor.setAccessible(true);//强制初始化Object o = constructor.newInstance();Object o1 = constructor.newInstance();System.out.println(o == o1);} catch (Exception e) {e.printStackTrace();}
强制初始化后,出现两个不同的实例,优化重复创建抛出异常
public class LazyInnerClassSingleton {private LazyInnerClassSingleton() {//加个判断if(LazyHolder.LAZY != null){throw new RuntimeException("不允许创建多个实例");}}//默认不加载private static class LazyHolder {private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();}//static是为了使单例的空间共享,保证方法不会被重写重载public static final LazyInnerClassSingleton getInstance() {//返回结果前,一定先加载内部类return LazyHolder.LAZY;}}
2)序列化破坏单例
单例创建好后,有时候需要将对象序列化再写入磁盘,读取的时候再进行反序列化,转化为内存对象。反序列化后的对象会重新分配内存。
public class SerializableSingleton implements Serializable {private SerializableSingleton() {}private final static SerializableSingleton INSTANCE = new SerializableSingleton();public static SerializableSingleton getInstance() {return INSTANCE;}}
SerializableSingleton s1 = null;SerializableSingleton s2 = SerializableSingleton.getInstance();FileOutputStream fos = null;try {fos = new FileOutputStream("SerializableSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s2);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerializableSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s1 = (SerializableSingleton) ois.readObject();ois.close();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);} catch (Exception e) {System.out.println(e);}
反序列化后的对象和手动创建的对象是不同的
优化代码:
public class SerializableSingleton implements Serializable {private SerializableSingleton() {}private final static SerializableSingleton INSTANCE = new SerializableSingleton();public static SerializableSingleton getInstance() {return INSTANCE;}private Object readResolve(){return INSTANCE;}}
原理:
- isInstantiable(),判断构造方法是否为空,不为空返回true,只要有无参构造方法就会实例化。
- hasReadResolveMethod(),就是通过反射找到一个无参的readResolve()方法并保存。
- invokeReadResolve(),反射调用readResolveMethod()
虽然readResolve()方法返回实例解决了单例模式被破坏的问题,但实际上是实例化了两次,只不过新创建的对象没有被返回。如果创建对象的动作发生频繁加快,就以为这内存分配开销也会随之增大。