Java 高效编程·单例模式的几种实现

作者 : jamin 本文共2757个字,预计阅读时间需要7分钟 发布时间: 2020-10-18 共1133人阅读

单例模式的几种实现

单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。Java 里面实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的 ClassLoad 装载实现单例类的时候就会创建一个类的实例。

单例模式的优点:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

单例模式的核心在于通过一个接口返回唯一的对象实例。首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类的实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法。

饿汉式

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}

    public static Singleton getInsatnce() {
        return instance;
    }
}

饿汉式提前实例化,没有懒汉式中多线程问题,但无法对 instance 实例做延时加载,单例会在加载类后一开始就被初始化,保证该实例只有一个,不管是否调用 getInstance() 都会存在一个实例在内存中。

此外饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

懒汉式

public class Singleton {
    private static Singleton instance;
    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式实现了实例延时加载,但是需要添加 synchronized 来保证线程安全。但这样做并不高效,因为在任何时候只能有一个线程调用 getInstance() 方法。

双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法,可以看作是上面懒汉式的改进版。双重检查锁会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。第一个 if 可以提升效率,为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

public class Singleton {
    private static Singleton instance;
    private Singleton() {}

    public static Singleton getSingleton() {
        if (instance == null) {                         // Single Checked
            synchronized (Singleton.class) {
                if (instance == null) {                 // Double Checked
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检验锁看起来很完美,但可惜它有问题。主要在于 instance = new Singleton() 这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将 instance 对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

这时需要将 instance 变量声明成 volatile 就可以了:

public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}

    public static Singleton getSingleton() {
        if (instance == null) {                         // Single Checked
            synchronized (Singleton.class) {
                if (instance == null) {                 // Double Checked
                    instance = new Singleton();
                }
            }
        }
        return instance ;
    }
}

使用 volatile 的主要原因是其一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

静态内部类

推荐使用静态内部类的方法,这种方法也是《Effective Java》上所推荐的。

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种写法使用 JVM 本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

枚举

同样也是《Effective Java》推荐的写法,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,但使用较少。

public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
    }
}

调用:

Singleton.INSTANCE.whateverMethod();

参考文章:
Java 编程设计模式-单例模式
如何正确地写出单例模式

本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
金点网络 » Java 高效编程·单例模式的几种实现

常见问题FAQ

免费下载或者VIP会员专享资源能否直接商用?
本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
是否提供免费更新服务?
持续更新,永久免费
是否经过安全检测?
安全无毒,放心食用

提供最优质的资源集合

立即加入 友好社区
×