单例模式(Singleton Pattern)

本文详细介绍了Java单例模式的基本概念,包括经典单例、多线程安全问题及解决方案(如双重检查锁模式),并对比分析了饿汉式和静态内部类实现方式,最后介绍了枚举作为单例的使用场景。旨在帮助开发者理解单例模式的优缺点及性能优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经典的单例

相信大家一定写过这样类似的单例模式代码:

public class Singleton {

    private static Singleton ins;
    private Singleton() {}
    public static Singleton getIns() {
        if (null == ins) {
            ins = new Singleton();
        }
        return ins;
    }
}

简单总结一下这样的写法:

  1. 提供一个全局静态的getIns方法,使得易于使用.
  2. 延迟Singleton的实例化,节省资源(所谓的懒汉式).
  3. 缺点是线程不安全.当多个线程同时进入if (null == ins) {}的时候就会创建多个实例.

OK,接下来我们看来是要解决多线程不安全的问题了.

多线程安全

这个时候可能有的人就说了,这个太简单了,加一个synchronized不就结了吗?

public static synchronized Singleton getIns() {
    if (null == ins) {
        ins = new Singleton();
    }
    return ins;
}

确实,增加synchronized之后能够迫使每个线程在进入这个方法之前,要先等别的线程离开该方法.也即避免了多个线程同时进入getIns方法.

诚然这个能解决问题,但是我们知道synchronized是非常耗性能的.
更何况:
我们只需要在第一次执行这个方法的时候同步,也就是说当ins实例化后,我们不再需要同步了
而如果我们加了synchronized,那么实例化后的每次调用getIns都是一种多余的消耗操作,是累赘

Ps:当然,如果哪些额外的负担你能接受(比如用的很少),那么添加`synchronized`的方法也是可以接受的,毕竟这是最简单的方式.

那么问题来了,如何改善?
如何确保单例,而又不影响性能?

性能进阶

接下去介绍一种更优秀的,线程安全的单例写法---双重检查锁模式(double check locking pattern)

public class DoubleCheck {

    private volatile static DoubleCheck ins;
    private DoubleCheck() {}
    public static DoubleCheck getIns() {
        if (null==ins){ //检查
            synchronized (DoubleCheck.class){
                if (null == ins) { //又检查一次
                    ins = new DoubleCheck();
                }
            }
        }
        return ins;
    }
}

注意这里的ins用了volatile关键字来修饰,为什么呢?
因为执行ins = new DoubleCheck()做了很多事情:

  1. 给ins分配内存
  2. 调用构造函数来初始化成员变量(可能会很久)
  3. 将ins对象指向分配的内存空间(执行完这步 ins才不为null)

上面的操作并不是原子操作,而jvm也可能重排序指令,导致第二三两步的执行顺序可能会被打乱,当第3步先于第2步完成,那么会导致有线程拿到了初始化未完毕的ins,那么就会错误,而这里利用了volatile禁止指令重排序优化特性,用来解决这个问题.

注:volatile 在java 5 后才有效,原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,当然现在不需要担心这个啦!

小结

双重检查非常适用于高并发,我们熟知的开源库Eventbus,ImageLoader等都是用的双重检查锁方式实现单例

不过它,写起来稍微复杂了些,有没有简单点的呢?
答案是:有!

饿汉式

直接上代码吧

public class Early {
    private static final Early ins = new Early();
    public static Early getIns() {
        return ins;
    }
}

饿汉式的原理其实是基于classloder机制来避免了多线程的同步问题

饿汉式与之前提到的懒汉式不同,它在我们调用getIns之前就实例化了(在类加载的时候就实例化了),所以不是一个懒加载,这样就有几个缺点:

  1. 延长了类加载时间
  2. 如果没用到这个类,就浪费了资源(加载了但是没用它)
  3. 不能传递参数(很显然适用的场景会减少)
静态内部类

静态内部类原理同上,另外虽然它看上去有点饿汉式,但是与之前的饿汉有点不同,它在类Singleton加载完毕后并没有实例化,而是当调用getIns去加载Holder的时候才会实例化,静态内部类的方式把实例化延迟到了内部类的加载中去了!所以它比饿汉式更优秀!(偷偷告诉你Effective Java中也推荐这个方式)

例子:

public class Singleton {

    private static class Holder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getIns(){
        return Holder.INSTANCE;
    }
}
枚举

最后介绍一种,也是我在 EffectiveJava中看到的,但是在开发中我重来没看到过!

public enum Singleton{
    INSTANCE;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值