彻底搞懂单例模式【一篇就够】

一、【饿汉模式】- 多线程安全单例模式实例一(不使用同步锁)

缺点:对象在没有使用之前就已经初始化了。这就可能带来潜在的性能问题:如果这个对象很大呢?没有使用这个对象之前,就把它加载到了内存中去是一种巨大的浪费。另外,当系统中这样的类较多时,会使得启动速度变慢 。

public class EagerSingleton {
    private static final EagerSingleton singleton = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getSingleton() {
        return singleton;
    }
}

二、【懒汉模式】多线程安全单例模式实例二(使用同步方法)

缺点:一次锁住了整个方法,粒度有些大。改进--只在实例化语句加锁

public class LazySingleton {
    private static LazySingleton singleton = null;

    private LazySingleton() {
    }

    public static synchronized LazySingleton getSingleton() {
        if (singleton == null)
            singleton = new LazySingleton();
        return singleton;
    }
}

三、【懒汉模式】多线程安全单例模式实例三(使用同步方法-双重检查锁Double-Checked Lock)

双重检查的原因:可能会有多个线程同时同步块外的if判断语句,不进行双重检查就会导致创建多个实例。

同时因为singleton = new LazySingleton()不是原子操作,而是分为3个步骤进行:

①给singleton分配内存 ②在空间内创建对象即初始化 ③将singleton变量指向分配的内存空间

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的②和③的顺序是不能保证的,最终的执行顺序可能是 ①②③也可能是 ①③②。如果是后者,则在③执行完毕、②未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后就会报错。
解决办法就是要将 singleton变量声明成 volatile 就可以了,因为volatile有禁止指令重排的特性。

Synchronized可依保证程序的有序性(即程序执行的顺序按照代码的先后顺序执行),但并无法禁止指令重排和处理器优化的。

[但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。]

public class LazySingleton {
    private volatile static LazySingleton singleton = null;

    private LazySingleton() {
    }

    public static LazySingleton getSingleton() {
        if (singleton == null) {
            synchronized (LazySingleton.class) {
                if (singleton == null)
                    singleton = new LazySingleton();
            }
        }

        return singleton;
    }
}

但是,实际还是可以通过反射方式创建多个实例:

public class SingletonTest {

    public static void main(String[] args) throws Exception{

        Class clz = Class.forName("LazySingleton");
        // "true" indicates that the reflected object should suppress(废弃) Java language access checking when it is used
        Constructor constructor = clz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object obj1 = constructor.newInstance();
        Object obj2 = constructor.newInstance();
    }
}

四、【懒汉模式】多线程安全单例模式实例四(使用静态内部类《Effective Java》上所推荐的)

外部类被加载时内部类并不需要立即加载内部类,内部类不被加载则不需要进行类初始化,因此单例对象在外部类被加载了以后不占用内存。

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

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

五、单例模式实例五(实际没有用过,待验证)

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

public enum EasySingleton{
    INSTANCE;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sjmz30071360

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值