设计模式之单例模式!
发表于更新于
单例模式就是在程序运行中只实例化一次,创建一个全局唯一对象。
饿汉模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SingletonObject { private static final SingletonObject instance = new SingletonObject(); private SingletonObject(){ } public static SingletonObject getInstance(){ return instance; } }
|
饿汉模式优缺点:
缺点:
- 不能实现懒加载,造成空间浪费。
- 如果一个类比较大,在初始化的时就加载了这个类,但是长时间没有使用这个类,这就导致了内存空间的浪费。
懒汉模式
在程序初始化时不会创建实例,只有在使用实例的时候才会创建实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class SingletonObject { private static SingletonObject instance; private SingletonObject(){ } public static SingletonObject getInstance(){ if (instance == null) instance = new SingletonObject(); return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class SingletonObject { private static SingletonObject instance; private SingletonObject(){ } public synchronized static SingletonObject getInstance(){
if (instance == null) instance = new SingletonObject(); return instance; } }
|
懒汉模式的优缺点:
优点:
缺点:
- 在不加锁的情况下,线程不安全,可能出现多份实例。
- 在加锁的情况下,会是程序串行化,使系统有严重的性能问题。
双重检查锁模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class SingletonObject { private static SingletonObject instance; private SingletonObject(){ } public static SingletonObject getInstance(){ if (instance == null) synchronized (SingletonObject.class){ if (instance == null){ instance = new SingletonObject(); } } return instance; } }
|
在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
1 2 3 4 5 6
| private SingletonObject(){ 1 int x = 10; 2 int y = 30; 3 Object o = new Object(); }
|
我们编写的顺序是1、2、3,JVM 会对它进行指令重排序。
- 所以执行顺序可能是3、1、2,也可能是2、3、1,不管是那种执行顺序,JVM 最后都会保证所以实例都完成实例化。
如果构造函数中操作比较多时,为了提升效率,JVM 会在构造函数里面的属性未全部完成实例化时,就返回对象。
双重检测锁出现空指针问题的原因就是出现在这里。
- 当某个线程获取锁进行实例化时,其他线程就直接获取实例使用。
- 由于JVM指令重排序的原因,其他线程获取的对象也许不是一个完整的对象,所以在使用实例的时候就会出现空指针异常问题。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile
关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private static volatile SingletonObject instance; private SingletonObject(){ } public static SingletonObject getInstance(){ if (instance == null) synchronized (SingletonObject.class){ if (instance == null){ instance = new SingletonObject(); } } return instance; } }
|
静态内部类单例模式
静态内部类单例模式实例由内部类创建。
由于 JVM
在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。
静态属性由static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class SingletonObject { private SingletonObject(){ } private static class InstanceHolder{ private final static SingletonObject instance = new SingletonObject(); } public static SingletonObject getInstance(){ return InstanceHolder.instance; } }
|
枚举类单例模式
枚举类型是线程安全的,并且只会装载一次。
设计者充分的利用了枚举的这个特性来实现单例模式。
- 枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
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 28 29
| public class SingletonObject { private SingletonObject(){ }
private enum Singleton{ INSTANCE; private final SingletonObject instance; Singleton(){ instance = new SingletonObject(); } private SingletonObject getInstance(){ return instance; } } public static SingletonObject getInstance(){ return Singleton.INSTANCE.getInstance(); } }
|
破坏单例模式的方法及解决办法
除枚举方式外,其他方法都会通过反射的方式破坏单例。
反射是通过调用构造方法生成新的对象。
- 所以如果想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例。
1 2 3 4 5 6 7 8 9
| Class objClass = SingletonObject.class;
Constructor constructor = objClass.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonObject staticInnerClass = SingletonObject.getInstance();
SingletonObject newStaticInnerClass = (SingletonObject) constructor.newInstance();
|
1 2 3 4 5
| private SingletonObject(){ if (instance !=null){ throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取"); } }
|
如果单例类实现了序列化接口Serializable
,就可以通过反序列化破坏单例。
所以可以不实现序列化接口。
- 如果非得实现序列化接口,可以重写反序列化方法
readResolve()
, 反序列化时直接返回相关单例对象。
1 2 3
| public Object readResolve() throws ObjectStreamException { return instance; }
|