java设计模式之单例模式

本文详细介绍了单例模式的概念及其在Java中的实现方式,包括饿汉模式、懒汉模式及使用枚举实现单例的方法,并讨论了多线程环境下单例模式的正确实现。

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

单例对象(Singleton)是一种常用的设计模式。

单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处

1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

1、单例(饿汉模式)代码:

<span style="font-size:12px;">public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
 
    //私有化构造方法,防止被实例化   
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return INSTANCE;
    }
}</span>

2、单例(懒汉模式)代码:

<span style="font-size:12px;"><span style="font-size:12px;">public final class LazySingleton 
{ 
    private static LazySingleton singObj = null; 
 
    private LazySingleton(){ 
    } 
 
    public static LazySingleton getSingleInstance(){ 
        if(null == singObj) singObj = new LazySingleton();
          return singObj;
    } 
} </span></span>

下面为优化的单例懒汉模式代码,注意静态字段声明的时候,有一个volatile关键字,并且代码中两次判断是否是null,这种双重检测的机制为了应对多线程环境

<span style="font-size:12px;">public class Singleton {
    private static volatile Singleton INSTANCE = null;
    //私有化构造方法,防止被实例化
    private Singleton() {
    }
    //双重检测
    public static Singleton getInstance() {
        if (INSTANCE == null) { //①
            synchronized (Singleton.class) { //②
                if (INSTANCE == null) { //③
                    INSTANCE = new Singleton(); //④
                }
            }
        }
        return INSTANCE;
    }
}</span>

需要注意的是,即使这种Doublecheck在C++中有效,但对JAVA5之前的代码还是有一点问题的。

原因在于,编译器会优化代码,可能导致赋值语句乱序执行,上述代码中,如果有2个线程A,B。A线程已经进入4位置,当4位置的代码执行时,需要注意 INSTANCE = new Singleton()这个行代码不是一个原子操作。

JVM可能在完成Singleton类的构造方法之前,会先把一块还未初始化完成的内存地址先分配给INSTANCE,而此时如果线程B进入1位置,会认为INSTANCE已经存在,从而返回了一个未初始化完成的内存块,这可能导致程序崩溃。正是由于是先给INSTANCE赋值在初始化内存块,还是先初始化内存块再复制给INSTANCE,这个顺序无法保证,所以这种机制会出现问。所以在JAVA5之后,扩充了 volatile关键字,确保一个变量写入和读取操作的顺其不会被编译器优化成乱序,volatile变量也不会被缓存到cpu寄存器中,保证了其读取的一致性。

这和C#中的volatile的关键字是一样的作用。

除了上面2中常见的方法之外,还有其他方法。比如下面一种比较经典的实现方法,使用一个内部类,JVM自身保证了自身安全,这个模式也是在《Effective Java》这本书中推荐的,这种方式不依赖于java版本。

<span style="font-size:12px;">public class Singleton {
    private Singleton() {
    }
    public static final Singleton getInstance() {
        return InnerClass.INSTANCE;
    } 
    private static class InnerClass {
        private static Singleton INSTANCE = new Singleton();
    }
}</span>

但在JAVA5之后,最简单的单例实现方法是使用enum类型

enum关键字是JAVA5中新增的,它和class,interface一样,也是一种数据类型。可以把它看成是一种特殊的类。可以在enum内部实现构造方法,字段,方法等,还可以实现接口。不过也有一些限定,枚举类中的构造器,默认为private修饰,且只能使用private。枚举类的所有实例必须在类中的第一行显式列出,否则这个枚举类不可能产生实例。JVM保证了这个每个枚举值只被初始化一次。正是由于这样一个特点,我们可以用如下代码可以实现单例。

<span style="font-size:12px;">public enum Singleton {INSTANCE;
    public void dosth(String arg) {
        // 逻辑代码
    }
}</span>

上述单例中,Singleton.INSTANCE就表示了一个单例。非常简单高效。

注意Java中enum关键字和C#中的enum,差别很大,C#中不能使用这种方式。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值