单例模式

本文详细介绍了单例模式的概念、作用以及基本实现方式,强调了单例模式确保类只有一个实例并提供全局访问点。同时,针对多线程环境下可能出现的问题,列举了三种解决方案,包括同步getInstance方法、由JVM初始化类时创建单例以及使用“double checked locking”优化性能。最后,提醒在存在多个类加载器时注意单例的一致性问题。

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

本篇博客主要记录一下单例模式。


单例模式大家应该都不陌生,主要用于创建一个“独一无二”的对象。
它基本的写法类似于:

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}

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

        return uniqueInstance;
    }
}

容易看出,单例模式其实就是利用一个静态变量uniqueInstance来记录Singleton的唯一实例。

Singleton的构造器被声明为私有的,因此只有内部方法可以调用。

当客户端代码试图获取单例对象时,必须调用其静态方法getInstance。
在getInstance方法中,将判断Singleton是否被实例化过。
如果实例化过,则直接返回uniqueInstance;否则,将先实例化单例对象。

通过这种方式,外部类不能随意地实例化Singleton;
仅能通过Singleton提供的接口,获取对象。
而Singleton又管制了自身的创建,于是最终保证Singleton“独一无二”。


单例模式的定义和结构图都很简单,如下所示:
单例模式确保一个类只有一个实例,并提供一个全局访问点。

如图所示,单例模式其实就只有一个类,
它主要是通过上文中代码的固定“套路”来保证对象的“独一无二”。


需要注意的是,在多线程的场景下,上述单例的实现方式,将会产生问题。

例如,在初次初始化时,两个线程同时进入到getInstance方法,
并判断uniqueInstance为null,此时将会初始化多个实例。

针对这个问题,在多线程场景下,主要有三种解决方案。

1、同步getInstance方法
按照这种方案修改后的代码类似于:

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}

    //增加synchronized关键字
    //迫使每个线程在进入该方法前,必须等待其它线程离开
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }

        return uniqueInstance;
    }
}

这种修改方案比较简单,唯一的缺点是对性能有影响。
如果getInstance将被频繁调用,那么就不能这么改。

2、由JVM初始化类时创建单例
按照这种方案修改后的代码类似于:

public class Singleton {
    //类被加载时,就创建出单例
    //保证了线程安全
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

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

一般单例被频繁使用,或创建和运行的负担不重时,可以采取这种方式。

3、利用“double checked locking”来同步getInstance方法

public class Singleton {
    //这里注意使用volatile关键字
    //保证一个线程创建单例后,另一个线程能够发现这种改变
    private volatile static Singleton uniqueInstance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            //只有第一次创建时,才会真正利用synchronized进行同步
            //因此对性能的影响较小
            synchronized (Singleton.class) {
                //进入同步块后,再次判断是否创建
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }

        return uniqueInstance;
    }
}

这种方式可以看作对第1种方式的性能优化。


最后,需要说明的是:
每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,
不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。
如果这种事情发生在单例上,就会产生多个单例并存的现象。

因此,如果程序中有多个类加载器又使用了单例模式,
那么最后指定同一个类加载器加载单例类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值