Java设计模式之单例模式的7种写法

本文详细介绍了单例模式的7种实现方式,包括饿汉式、懒汉式及其升级版、双重校验锁、静态内部类和枚举。分析了每种方式的优缺点,重点讨论了线程安全和性能问题,推荐使用静态内部类和枚举实现单例模式。

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

简介

单例模式是23种设计模式中比较基础的设计模式了。什么是单例呢,就是全局唯一的一个实例,一个类只有一个实例,只能通过唯一的入口来获取实例。

下面就跟大家分享下7种实现方式

饿汉式

顾名思义,取名叫饿汉式,就是一开始创建实例。

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

    private Hungry() {}

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

优点: 实现简单,并且是线程安全的。

缺点: 在类加载的时候就初始化实例了,如果程序很久才使用该实例或者一直没有使用的话,该实例会一直在内存中,占用内存。所以有了下面的懒汉式。

懒汉式

懒汉式,就是延迟加载,在我们需要使用单例的实例的时候再创建。

public class Lazyload {
    private static Lazyload instance = null;

    private Lazyload() {}

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

优点: 实现简单,并且是延迟加载的,在我们需要使用单例的实例调用getInstance方法的时候再创建。

缺点: 线程不安全的,如果同时有线程1和线程2调用getInstance方法,都判断instance为空,这个时候线程1和线程2都会创建一个实例,从而不能保证实例唯一性,因为线程1和线程2都是用的自己创建的实例。

懒汉式升级版(同步)

懒汉式,就是延迟加载,在我们需要使用单例的实例的时候再创建。

public class Lazyload {
    private static Lazyload instance = null;

    private Lazyload() {}

	// 同步方法
    public synchronized static Lazyload getInstance() {
        if (null == instance) {
            instance = new Lazyload();
        }
        return instance;
    }
}

优点: 实现简单,并且是延迟加载的,在我们需要使用单例的实例调用getInstance方法的时候再创建,并且解决了线程安全问题,保证了实例的唯一性。

缺点: 虽然是线程安全的,但是synchronized关键字修饰的getInstance同一时间只能有一个线程可以访问,其他线程都要等待,会影响性能。

双重校验锁

为了解决上面一种方式的问题,设计思想是对instance实例进行两次检查,第一次判断instance是否为空,如果为空再加锁进行为空判断,如果还是为空就创建实例。这种方式可以提高性能,因为每个线程都可以访问getInstance方法。

public class DoubleCheck {
    private static DoubleCheck instance = null;
    private DoubleCheck() {}

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

缺点: 这个方式看似很完美了,但是还是会有隐藏问题,为什么呢,我们来看看instance = new DoubleCheck()这行代码,这个初始化并非原子性操作,而是有3步:

  1. 给instance分配内存
  2. 调用DoubleCheck构造方法初始化
  3. 将instance对象的引用指向分配的内存空间(一旦执行这一步,instance就不是null了)

但是JVM在即时编译的时候存在指令重排序的优化,那么上面的第二和第三步就有可能交换顺序,则执行顺序有可能是1-2-3,也有可能是1-3-2,如果是1-3-2的话,假如线程1在第三步执行完成过后,线程2刚好正要执行第一次(第6行)的null == instance判断,这个时候线程2发现instance已经不为空了,那么就直接将instance返回使用了,但是线程2使用的是还没有初始化的instance实例。

双重校验锁升级版(volatile)

解决双重校验的指令重排序的问题,可以使用volatile关键字,来防止指令重排序,那么优化后的代码如下

public class DoubleCheck {
    private static volatile DoubleCheck instance = null;
    private DoubleCheck() {}

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

静态内部类

public class Singleton {
    private Singleton() {}

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

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

优点: 延迟加载,当调用getInstance方法才会加载私有内部类进行实例化,并且是线程安全的,性能高。建议使用。

枚举

public enum SingletonEnum {
    INSTANCE;
    public static SingletonEnum getInstance() {
        return INSTANCE;
    }
}

或者

public class SingletonEnum {
    private SingletonEnum() {

    }
    public static SingletonEnum getInstance() {
        return HolderEnum.INSTANCE.getInstance();
    }

    private enum HolderEnum {
        INSTANCE;
        private SingletonEnum instance;
        HolderEnum() {
            this.instance = new SingletonEnum();
        }
        private SingletonEnum getInstance() {
            return instance;
        }
    }
}

创建枚举类默认就是线程安全的,而且还能防止反序列化导致重新创建新的对象。也是建议使用的。

以上就是7中单例模式的实现方式,如果有理解或者写的不对的地方,欢迎指出,共同学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值