一、懒汉式

  1. /**
  2. * 懒汉式
  3. * 优点:需要的时候才把对象创建出来
  4. * 缺点:由于加了 synchronized 锁,所以并发时效率较低
  5. */
  6. public class Singleton1 {
  7. private static Singleton1 singleton;
  8. private Singleton1() {}
  9. public static synchronized Singleton1 getInstance() {
  10. if (singleton == null) {
  11. singleton = new Singleton1();
  12. }
  13. return singleton;
  14. }
  15. }

二、饿汉式

  1. /**
  2. * 饿汉式
  3. * 优点:没有加锁,性能高
  4. * 缺点:在类装载时就实例化,浪费内存
  5. */
  6. public class Singleton2 {
  7. private static Singleton2 singleton = new Singleton2();
  8. private Singleton2() {}
  9. public static Singleton2 getInstance() {
  10. return singleton;
  11. }
  12. }

三、双重校验锁

  1. /**
  2. * 双重校验锁
  3. * 优点:需要的时候才创建对象,由于获取对象方法没有加锁,所以性能较高
  4. * 缺点:实现较复杂
  5. */
  6. public class Singleton3 {
  7. private static Singleton3 singleton;
  8. private Singleton3() {}
  9. public static Singleton3 getInstance() {
  10. if (singleton == null) {
  11. synchronized (Singleton3.class) {
  12. if (singleton == null) {
  13. singleton = new Singleton3();
  14. }
  15. }
  16. }
  17. return singleton;
  18. }
  19. }

四、枚举

  1. public enum Singleton4 {
  2. INSTANCE;
  3. }

优势 1:一目了然的代码

优势 2:天然的线程安全与单一实例

它不需要做任何额外的操作,就可以保证对象单一性与线程安全性。

优势 3:枚举保护单例模式不被破坏

使用枚举可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式。
防反射
枚举类默认继承了 Enum 类,在利用反射调用 newInstance() 时,会判断该类是否是一个枚举类,如果是,则抛出异常。
image.png
防止反序列化创建多个枚举对象
在读入 Singleton 对象时,每个枚举类型和枚举名字都是唯一的,所以在序列化时,仅仅只是对枚举的类型和变量名输出到文件中,在读入文件反序列化成对象时,使用 Enum 类的 valueOf(String name) 方法根据变量的名字查找对应的枚举对象。
所以,在序列化和反序列化的过程中,只是写出和读入了枚举类型和名字,没有任何关于对象的操作。
单例模式实现总结 - 图2

五、静态内部类

  1. /**
  2. * 静态内部类
  3. * 优点:实现简单,需要使用时才加载,节约内存
  4. */
  5. public class Singleton5 {
  6. private static class SingletonHolder {
  7. private static final Singleton5 INSTANCE = new Singleton5();
  8. }
  9. private Singleton5() {}
  10. public static Singleton5 getInstance() {
  11. return SingletonHolder.INSTANCE;
  12. }
  13. }

破坏单例的方式

利用反射破坏单例模式

利用反射,强制访问类的私有构造器,去创建另一个对象

  1. public static void main(String[] args) {
  2. // 获取类的显式构造器
  3. Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();
  4. // 可访问私有构造器
  5. construct.setAccessible(true);
  6. // 利用反射构造新对象
  7. Singleton obj1 = construct.newInstance();
  8. // 通过正常方式获取单例对象
  9. Singleton obj2 = Singleton.getInstance();
  10. System.out.println(obj1 == obj2); // false
  11. }

利用序列化与反序列化破坏单例模式

使用序列化和反序列化破坏单例模式

  1. public static void main(String[] args) {
  2. // 创建输出流
  3. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
  4. // 将单例对象写到文件中
  5. oos.writeObject(Singleton.getInstance());
  6. // 从文件中读取单例对象
  7. File file = new File("Singleton.file");
  8. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
  9. Singleton newInstance = (Singleton) ois.readObject();
  10. // 判断是否是同一个对象
  11. System.out.println(newInstance == Singleton.getInstance()); // false
  12. }