Java 单例模式

Java 单例模式

单例模式(Singleton)是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。提供一个访问该实例的全局方法(通常名为 getInstance())。

Java 里面实现的单例是一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的 ClassLoad 装载实现单例类的时候就会创建一个类的实例。

在 Java 语言中,这样的行为能带来两大好处:

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

因此对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效地改善系统的性能。

单例模式的核心在于通过一个接口返回唯一的对象实例。

首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类的实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法。

单例模式基本实现

public class Singleton {
    private Singleton() {}

    private static Singleton instance = new Singleton();

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

几个关键点:

  1. private 的构造函数是防止单例被其它代码实例化(实例化动作由类内部自行控制)。
  2. instance 成员必须是 static 定义的,只允许类拥有一个实例化对象(单例对象)。

几种单例模式对比

线程安全并发性能好支持延迟加载序列化/反序列化安全备注
饿汉式YY
懒汉式(不加锁)YY
懒汉式(加锁)Y
懒汉式(双重校验锁)YY
懒汉式(双重校验锁 + volatile)( Y )YYJava 5 以上安全
静态内部类YYY
枚举YYY

主要说明一下饿汉式和懒汉式:

饿汉式:指全局的单例实例(instance 的初始化)在类装载时构建。

懒汉式:指全局的单例实例(instance 的初始化)在第一次被使用时构建。

下面来详细讲讲每种单例模式。

I. 饿汉式

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

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

缺点: 提前装载会导致资源的不必要浪费。

II. 懒汉式(不加锁)

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

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

缺点: 多线程调用 getInstance 容易同时创建多个实例,不能正常工作。

II. 饿汉式(加锁)

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

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

缺点: 多线程下调用耗时增加。

其实实例只会创建一次,却因为对整个 getInstance 接口加锁,明显浪费。

所以有 饿汉式(加锁) 的变种:

III 饿汉式(双重检验锁)

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

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

缺点:由于 instance = new Singleton() 这一步并非原子操作,存在 instance 非 null,但 instance 还没初始化的情况。此时调用会引起出错。

instance = new Singleton() 主要步骤:

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

但是 JVM 的即时编译器中存在指令重排序的优化。即上述第 2 和第 3 步的顺序并不能保证。

如果执行顺序为: 1 -> 3 -> 2。那么执行完第 3 步后, instance 非 null,此时其它线程访问时会在第一个 null 判断处返回 instance

所以还有 饿汉式(双重校验锁) 的变种:

IV 饿汉式(双重检验锁 + volatile)

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

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

volatile 并不是要使用其 “每次从主内存中读取信息,本地线程不保留 instance 副本” 的特性。

而是因为 volatile 的另一个特性:禁止指令重排序优化

在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

这也就避免出现 1 -> 3 -> 2 因此 instance 没初始化的访问,导致出错的问题。

缺点:Java 5 以前的 JMM (Java 内存模型)存在缺陷,使用 volatile 也不能完全避免重排序。所以这种方式并不 “通用” 。

V. 静态内部类

public class Singleton {
    private Singleton (){}

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

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

这种写法使用 JVM 本身的机制保证线程安全问题;

由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它;

且只有 getInstance() 访问时,第一次触发 SingletonHolder 的 class 加载才会生成 INSTANCE 实例,因此它也是懒汉式的;

同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

VI. 枚举

public enum Singleton {
    INSTANCE;
}

通过 Singleton.INSTANCE 的访问形式访问(前面的单例访问 Singleton.getInstance())。

使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。

就是习惯性问题,对大多数人来说用 enum class 当普通 class 使用,会有不习惯。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值