单例模式
概念
单例模式:指一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点(getInstance方法)。
大概实现就是隐藏其构造方法,单例模式属于创建型模式。
一些实际的应用场景比如,DBpool, ServletContext,ServletConfig等
单例模式写法
饿汉式单例
在单例类首次加载时创建实例;
1 2 3 4 5 6 7 8 9
| public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){ return hungrySingleton; } }
|
优点
执行效率高,没有加任何锁
缺点
类加载的时候就初始化,在某些情况下,可能会造成内存浪费;如果出现类的数量很多的时候,会初始化很多类,占用大量内存;
局限性
Spring就不能使用,Spring启动的时候,会有大量的类加载。
饿汉式的第二种写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton ;
static { hungrySingleton = new HungryStaticSingleton(); }
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){ return hungrySingleton; } }
|
区别仅仅实在与类加载的顺序不同。👇
懒汉式单例
被外部类调用时才创建实例;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
|
优点
节省了内存
缺点
不能保证真实单例,线程不安全
线程不安全的原因
- 后面的线程覆盖掉前面线程创建的实例
- 同时进入判断条件,按顺序返回,没有覆盖的时候,就返回实例
解决方法:
1 2 3 4 5 6
| public static synchorized LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; }
|
但是getInstance方法添加上锁之后,性能下降,如果有很多请求访问,除了获得锁的线程之外,其他线程都要等待。
如何优化?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LazyDclSingleton {
private static LazyDclSingleton instance = null;
private LazyDclSingleton() { } public static LazyDclSingleton getInstance() { if (instance == null) { synchronized (LazyDclSingleton.class) { instance = new LazyDclSingleton(); } } return instance; } }
|
双重检查锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class LazyDclSingleton {
private static volatile LazyDclSingleton instance = null;
private LazyDclSingleton() { }
public static LazyDclSingleton getInstance() { if (instance == null) { synchronized (LazyDclSingleton.class) { if (instance == null) { instance = new LazyDclSingleton(); } } } return instance; } }
|
局限性
会出现指令重排序的问题,有可能返回一个不完整的实例
解决方案:private static volatile LazyDclSingleton instance = null;(volatile禁止指令重排序)
优点
性能高,能保证线程安全
缺点
代码可读性查,不够美观,代码不够优雅
静态内部类写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){} public static LazyInnerClassSingleton getInstance(){ return LazyHolder.instance; }
private static class LazyHolder{ private static final LazyInnerClassSingleton instance = new LazyInnerClassSingleton(); } }
|
优点
1、写法优雅,利用了java语言语法
2、性能也高
3、避免内存的浪费
缺点
1、能够被反射破坏单例
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class ReflectTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class<?> clazz = LazyInnerClassSingleton.class; Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); Object instance = declaredConstructor.newInstance(); System.err.println(instance); } }
|
解决办法: 在构造器中添加一个判断,如果实例已经创建,则直接抛出异常终止创建;
1 2 3 4 5
| private LazyInnerClassSingleton(){ if (LazyHolder.instance != null){ throw new IllegalArgumentException(); } }
|
注册式单例
将每一个实例都缓存到一个容器中,使用唯一标志获取实例
枚举写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public enum EnumSingleton {
INSTANCE;
private Object object;
public Object getObject() { return object; }
public void setObject(Object object) { this.object = object; }
public static EnumSingleton getInstance(){ return INSTANCE; } }
|
使用与测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class EnumSingletonTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingleton enumSingleton = EnumSingleton.getInstance(); enumSingleton.setObject(new Object());
Class<?> clazz = EnumSingleton.class; Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class); System.err.println(declaredConstructor); declaredConstructor.setAccessible(true); Object object = declaredConstructor.newInstance(); System.err.println(object); } }
|
测试结果:
1 2 3 4
| private com.ibli.javaBase.pattern.singleton.EnumSingleton(java.lang.String,int) Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.ibli.javaBase.pattern.singleton.EnumSingletonTest.main(EnumSingletonTest.java:17)
|
原因在JDK底层源码中已经做了限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @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; if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
|
优点
写法优雅,使用方便
缺点
和饿汉式一样,在某些情况下会造成大量内存浪费
容器式单例写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class ContainerSingleton {
private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getInstance(String className) { Object instance = null; if (!ioc.containsKey(className)) { try { instance = Class.forName(className).newInstance(); ioc.put(className, instance); } catch (Exception e) { e.printStackTrace(); } return instance; } else { return ioc.get(className); } } }
|
测试类:
1 2 3 4 5 6 7
| public class ContainerSingletonTest { public static void main(String[] args) { Object o1 = ContainerSingleton.getInstance("com.ibli.javaBase.pattern.singleton.Pojo"); Object o2 = ContainerSingleton.getInstance("com.ibli.javaBase.pattern.singleton.Pojo"); System.err.println(o1 == o2); } }
|
容器式单例写法适合创建大量单例实例的场景,类似与Spring的IOC容器。
当然上面的写法也会存在一个线程安全问题
序列化破坏单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class SerializableSingleton implements Serializable {
private static final SerializableSingleton serializableSingleton = new SerializableSingleton();
private SerializableSingleton() { }
public static SerializableSingleton getInstance() { return serializableSingleton; } }
|
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class SerializableSingletonTest { public static void main(String[] args) { SerializableSingleton s1; SerializableSingleton s2 = SerializableSingleton.getInstance();
FileOutputStream fos; 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) { e.printStackTrace(); } } }
|
结果:
1 2 3
| com.ibli.javaBase.pattern.singleton.SerializableSingleton@20ad9418 com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 false
|
解决方法:在SerializableSingleton中添加一个方法
1 2 3 4
| private Object readResolve() { return serializableSingleton; }
|
结果:
1 2 3
| com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 com.ibli.javaBase.pattern.singleton.SerializableSingleton@681a9515 true
|
原因:ois.readObject();方法底层有对readResolve方法的判断,如果不存在这个方法,会利用反射生成一个新的实例;
ThreadLocal单例
下面介绍一种比较少见的一种单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } };
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){ return threadLocalSingleton.get(); } }
|
1 2 3 4 5 6
| public class ThreadLocalExector implements Runnable{ @Override public void run() { System.err.println(ThreadLocalSingleton.getInstance()); } }
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12
| public class ThreadLocalSingletonTest {
public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance());
Thread thread1 = new Thread(new ThreadLocalExector()); Thread thread2 = new Thread(new ThreadLocalExector()); thread1.start(); thread2.start();; } }
|
结果:
1 2 3 4 5
| com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@38af3868 1 com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@38af3868 2
com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@10c69a60 3 com.ibli.javaBase.pattern.singleton.ThreadLocalSingleton@2f1eeb2f 4
|
以上3和4的结果虽然不一样,但是其实也是实现了【单例】的效果。
山脚太拥挤 我们更高处见。