作为一种创建型设计模式,单例模式能够保证一个类只有一个实例。
单例模式思路:
- 将默认的构造函数设为私有。
- 使用静态方法getInstance方法获取实例对象。
实现方案
单例模式可分成两种:
- 饿汉式:类加载时创建实例对象
- 饿汉式:类加载时不会创建实例对象,首次使用时才会创建
饿汉式
静态变量实现
public class SingleTon { private SingleTon() { } private static final SingleTon instance = new SingleTon(); public static SingleTon getInstance() { return instance; } }
|
静态代码块方式
public class SingleTon { private SingleTon(){} private static final SingleTon instance; static { instance = new SingleTon(); } public static SingleTon getInstance(){ return instance; } }
|
枚举方式⭐
public enum Singleton{ INSTANCE; }
|
借助枚举类的线程安全和只会装载一次的特性,实现了单例中唯一一种不会被破坏的单例模式。
饿汉式在没有被使用时就已经创建了,会造成内存浪费问题。
懒汉式
线程不安全实现方式
public class SingleTon { private SingleTon(){} private static SingleTon instance; public static SingleTon getInstance(){ if(instance==null) instance = new SingleTon(); return instance; } 」
|
线程安全的实现方式
public class SingleTon { private SingleTon(){} private static SingleTon instance; public static synchronized SingleTon getInstance(){ if(instance==null) instance = new SingleTon(); return instance; } }
|
使用synchronized
保证并发场景下的线程安全,缺点也很明显,性能差。
双重检查实现线程安全
public class SingleTon {
private SingleTon(){} private static volatile SingleTon instance; public static synchronized SingleTon getInstance(){ if(instance==null){ synchronized (SingleTon.class){ if(instance == null) instance = new SingleTon(); } } return instance; } }
|
双重检查锁可以降低锁的粒度,因为大多数事件instance是不为null
的,因此第一次检查不使用synchronize
,如果第一次检查是null
,才需要使用锁。注意要使用volatile
来防止指令重排。
静态内部类实现
public class SingleTon { private SingleTon(){}; private static class SingleTonHolder{ private static final SingleTon singleTon = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHolder.singleTon; } }
|
JVM加载外部类的过程中,不会加载静态内部类,只有在静态内部类的属性和方法被调用时才会被加载,可以保证线程安全和唯一性。(由JVM保证)
破坏单例
序列化与反序列化
通过对单例对象的序列化再反序列化可破坏单例模式。
try{ SingleTon singleTon = SingleTon.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./test.txt")); oos.writeObject(singleTon); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./test.txt")); SingleTon singleTon2 = (SingleTon) ois.readObject(); System.out.println(singleTon2==singleTon); } catch (Exception e){ System.out.println(e); }
|
解决方法:增加readResolve()方法,在反序列化时如果发现存在该方法将直接返回原对象。
public Object readResolve(){ return instance; }
|
反射
通过反射机制可以获得单例类的构造方法,进而更改构造方法的访问性,借此可在外部new 新对象。
try{ Class singleTonClass = SingleTon.class; Constructor constructor = singleTonClass.getDeclaredConstructor(); constructor.setAccessible(true); SingleTon singleTon1 = (SingleTon) constructor.newInstance(); SingleTon singleTon2 = (SingleTon) constructor.newInstance(); System.out.println(singleTon1 == singleTon2); } catch (NoSuchMethodException e){ System.out.println(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); }
|
解决方法:在构造方法中判断是否已经存在instance了,如果存在则直接抛出异常。
private SingleTon(){ if(instance !=null){ throw new RuntimeException(); } };
|