Skip to content

Latest commit

 

History

History
101 lines (83 loc) · 3.28 KB

单例.md

File metadata and controls

101 lines (83 loc) · 3.28 KB

单例模式 singleton

重点等级::star::star::star::star::star:

场景

单例类只能有一个实例;必须自己创建自己的唯一实例;必须给所有其他对象提供这一实例;
单例可以解决全局使用的类频繁地创建与销毁的问题,以节省系统资源

  • 日志类
  • 管理数据库连接
  • 文件管理

JDK源码中的案例

  • java.lang.Runtime.getRuntime() 饿汉式
  • java.awt.Desktop.getDesktop() 懒汉式,synchronized块

Best Way

单元素的枚举类型是实现单例的最佳方法。

Joshua Bloch, Effective Java 2nd Edition p.18
A single-element enum type is the best way to implement a singleton
enum since jdk1.5

public enum EnumIvoryTower {
  INSTANCE;
  // Other methods
}

EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
assertEquals(enumIvoryTower1, enumIvoryTower2); // true

Other Way

懒饿汉及双重检查锁

关键点

/*
 * 单例 - 双重检查锁
 * DCL (double-checked locking)
 */

final class Singleton {

    private volatile static Singleton _instance;
    // 饿汉式,直接初始化,线程安全
    // private static Singleton _instance = new Singleton();

    private static boolean flag = true;

    private Singleton() {
        // 构造方法私有化
        // 阻击反射攻击,此方法只能调用一次
        if (flag) {
          flag = false;
        } else {
          throw new IllegalStateException("Already initialized.");
        }
    }

    /*
     * 版本1: 懒汉模式,当多个线程同时访问,发生线程安全问题
     */
    public static Singleton getInstance() {
        if (_instance == null) {//饿汉式,因为已经初始化无需判断
            _instance = new Singleton();
        }
        return _instance;
    }

    /*
     * 版本2 : 对Singleton进行双重检查锁定的实现。
     * 目的是最大程度地减少同步成本并提高性能, 仅通过锁定代码的关键部分,不在方法上使用 synchronized
     * 如果我们不对 _instance 使用 volatile,那么这仍然是不安全的,因为另一个线程可以查看Singleton的一半初始化实例。
     */
    public static Singleton getInstanceDC() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null) {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }
}

这种实现模式有几个问题

  • 可以使用反射调用私有构造方法。
    AccessibleObject.setAccessible(true) 。 使用反射创建出来的来个对象指定是不一样的。
    如果要抵御这种攻击,可以修改构造方法,让它在被要求创建第二个实例的时候抛出异常。
  • 使用序列化前后的两个对象不相等。任何一个 ObjectInputStream.readObject 方法,它都会返回一个新建的实例。

而使用枚举实现单例可以避免以上问题。