GoF23—单例模式

单例模式:

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

单例模式的几种实现方式:

1、饿汉式

是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易

描述:这种方式比较常用,但容易产生垃圾对象。


优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

//饿汉式
public class SingleOn {

    //创建一个SingleObject 的一个对象
    public static SingleOn single = new SingleOn();

    //让构造函数为private,这样类就不会被实例化
    private SingleOn(){

    }

    //获取唯一可用的对象
   public static SingleOn getInstance(){
        return single;
   }

   private void showMessage(){
       System.out.println("你好,酷小亚!这是单例模式中的饿汉式方法");
   }

    public static void main(String[] args) {
        /**
         * 不合法的构造函数
         * 编译时错误:构造方法 SingleObject() 是不可见的
         * SingleObject object = new SingleObject();
         */
      
        //获取唯一可用的对象
        SingleOn instance = SingleOn.getInstance();
        //显示消息
        instance.showMessage();
    }
}

2、懒汉式 线程不安全

是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。


public class LazyMan {
    
    public static LazyMan lazyMan;

    private LazyMan(){}

    public static LazyMan getInstance() {
        if(lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    private void showMessage(){
        System.out.println("这是懒汉式,但线程不安全");
    }
    public static void main(String[] args){
        LazyMan instance = LazyMan.getInstance();
        instance.showMessage();
    }
    
}

3、懒汉式 线程安全

是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。


优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率


public class LazyMan {

    public static LazyMan lazyMan;

    private LazyMan(){}

    public static synchronized LazyMan getInstance() {
        if(lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    private void showMessage(){
        System.out.println("这是懒汉式,由于加了synchronized,所以线程安全");
    }
    public static void main(String[] args){
        LazyMan instance = LazyMan.getInstance();
        instance.showMessage();
    }

}

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。


public class DCL {

	//为防止指令重排 :加上 volatile 即可!
    public volatile static DCL dcl;

    private DCL() {
    }

    public static DCL getInstance() {

        if (dcl == null) {
            synchronized (DCL.class) {
                if (dcl == null) {
                    dcl = new DCL();//不是原子性
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     * 123  132
                     * 为防止指令重排 :加上 volatile 即可!
                     */
                }
            }
        }
        return dcl;
    }

    private void showMessage(){
        System.out.println("这是DCL(双重校验锁)");
    }

    public static void main(String[] args) {
        DCL instance = DCL.getInstance();
        instance.showMessage();
    }
}

5、登记式/静态内部类

是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。


public class Holder {

    public static class InnerClass{
        private static final Holder holder = new Holder();
    }

    private Holder(){}

    public static Holder getInstance() {
        return InnerClass.holder;
    }

    private void showMessage(){
        System.out.println("这是静态内部类");
    }
    public static void main(String[] args){
        Holder instance = Holder.getInstance();
        instance.showMessage();
    }
}

6、枚举

JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

//enum 是什么? 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;

    public static EnumSingle getInstance() {
        return INSTANCE;
    }
}
测试:枚举是否被破坏单例!
package single.demo2;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//enum 是什么? 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;

    public static EnumSingle getInstance() {
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(enumSingle);

    }
}

在这里插入图片描述

总结:

  • 一般情况下,建议使用第1 种饿汉方式,不建议使用第 2 种和第 3 种懒汉方式。
  • 如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式
  • 只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式
  • 如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

已转行@

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

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

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

打赏作者

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

抵扣说明:

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

余额充值