Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >详解设计模式@单例的进化之路

详解设计模式@单例的进化之路

作者头像
堆栈哲学
发布于 2023-03-08 07:05:29
发布于 2023-03-08 07:05:29
24100
代码可运行
举报
文章被收录于专栏:博客·技术专栏博客·技术专栏
运行总次数:0
代码可运行

概念

单例模式(Singleton Pattern)是设计模式中一个重要的模式之一,是确保一个类在任何情况下都绝对只有一个实例。单例模式一般会屏蔽构造器,单例对象提供一个全局访问点,属于创建型模式

根据初始化时间的不同,可以将单例模式分为两类:

  • 饿汉式单例
  • 懒汉式单例

当然,除了上面的两个分类之外,处于对性能、安全等方面的考量,单例模式还演化出了各种实现版本,每一种版本的演进,都是单例的一次进化与升级,下面就来看看单例模式的进化之路上都经历了哪些挑战与对抗。

饿汉式单例

饿汉式单例,特指在 类加载的时候就立即初始化并创建单例对象的一种单例模式写法。由于是在线程还没有出现之前就被实例化了,所以这种模式下的单例是线程绝对安全的,不存在访问安全的问题。

根据具体的实现方式划分,饿汉式单例可以通过 私有化构造器以及 使用静态代码块的方式具体实现。

  • 私有化构造器写法:HungrySingleton.java
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:32
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 饿汉式单例-构造器私有化写法
 */
public class HungrySingleton {
    private static final HungrySingleton hungrSingleton  = new HungrySingleton();

    private HungrySingleton() {};

    public static HungrySingleton getInstance() {
        return hungrSingleton;
    }
}

上面的代码中,我们将构造器进行了私有化之后,无法再通过new来创建对象,这种实现下,只能通过提供的getInstance()方法来获得单例对象。

  • 静态代码块写法:HungryStaticSingleton.java
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package 设计模式.单例模式.饿汉式单例;

/**
 * @author: 八尺妖剑
 * @date: 2022/4/23 8:36
 * @description: 饿汉式单例静态块写法
 * @blog:www.waer.ltd
 */
@SuppressWarnings({"all"})
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungryStaticSingleton;


    static {
        hungryStaticSingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){

    }

    public static HungryStaticSingleton getInstance(){
        return hungryStaticSingleton;
    }
}
  • 测试类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package ltd.waer.javabaseforio.PatternDesign;

@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:42
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 饿汉式单例测试类
 */
public class HungrySingletonTest {
    public static void main(String[] args) {
        //私有构造器写法
        HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
        HungrySingleton hungrySingleton2 = HungrySingleton.getInstance();
        System.out.println(hungrySingleton1 == hungrySingleton2);

        //静态块初始化写法
        HungryStaticSingleton singleton3 = HungryStaticSingleton.getInstance();
        HungryStaticSingleton singleton4 = HungryStaticSingleton.getInstance();
        System.out.println(singleton3==singleton4);
    }
}

测试结果:true。说明两种方式实现的单例都是有效的,因为不论我们调用多少次 getInstance 方法最后返回的就是同一个对象

优缺点:

创建的对象没有添加任何锁,执行效率高。

由于是在类加载的时候就初始化了,所以不管我们使用与否,它都将占有一定的内存空间,这种情况下,通过项目中存在了大量的单例,那么所占用的内存量就很可观了,着实浪费。

懒汉式单例

那么针对上述饿汉式单例存在的空间占用问题,有没有合适的替换或者解决方案呢?那么有请懒汉出场。 见名知意, 懒汉式单例饿汉式单例的理念刚好相反。它不会在 类加载的时候就初始化,而是等到用到了才会初始化,就这点来说,确实很 懒汉,不饿不吃饭(似乎有点道理??我不饿的时候也不想吃饭)。

到这里,单例模式就开始自己的进化之路了,下面列一下进化路线

进化主线:

  • 普通非线程安全单例
    • sync线程安全单例
      • 双重检查锁单例
        • 内部类单例
          • 枚举式单例

打野副本:

  • 内部类单例
    • 注册式单例
    • 单线程安全单例
      • 枚举式单例

1. 普通非线程安全单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:47
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 版本一:非线程安全
 */
public class LazySingleton {
   private static LazySingleton lazySingleton = null;

   private LazySingleton() {

   };

   public static LazySingleton getInstance() {
       if (null == lazySingleton) {
           lazySingleton = new LazySingleton();
       }
       return lazySingleton;
   }

    public static void main(String[] args) {
        LazySingleton instance1 = LazySingleton.getInstance();
        LazySingleton instance2 = LazySingleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}

上面是单例的最简单写法,也是最初的一种版本,在开始时将实例赋值为null,并没有进行初始化,而是在调用getInstance方法的时候才会初始化,虽然实现简单,但也存在线程安全问题,多线程环境下有一定几率会返回多个单例对象,这显然违背了单例的原则,进一步的解决办法就是下面这种实现。使用synchronizeed关键字保证线程安全。

2. sync线程安全单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:51
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 线程安全的懒汉式单例-synchronized
 */
public class LazySyncSingleton {
    private static LazySyncSingleton lazySyncSingleton = null;

    private LazySyncSingleton() {};

    public synchronized LazySyncSingleton getInstance () {
        if (null == lazySyncSingleton) {
            lazySyncSingleton = new LazySyncSingleton();
        }
        return lazySyncSingleton;
    }
}

上面的实现也非常简单,在前面一种写法的基础山加了一个synchronized关键字即可,这样确实解决了线程安全的问题,但也引出了一个新的问题,假如单例对象的创建非常复杂耗时的情况下,一旦并发量上来了,CPU压力上升,那么可能会导致大批量线程出现阻塞的情况,从而导致程序的允许性能大幅下降,解决方法是**双重检查锁(double-checked locking)**单例写法,如下:

3. 双重检查锁单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 9:59
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 解决写法2的问题,双重检查锁写法
 */
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton () {

    };

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

这种写法中,较于上面的写法做了两个地方的改变。

  • lazyDoubleCheckSingleton属性加上了volatile关键字,原因就是为了解决多线程下可见性问题,因为我们的getInstance方法在判断lazyDoubleCheckSingleton是否为null时并没有加锁,所以假如线程1初始化了对象,另外线程2是无法感知的,而加上了volatile之后便可以解决这个问题。
  • synchronized关键字移到了方法内部,尽可能缩小加锁的代码块,提升效率

迭代了这几个版本,到这里是否就已经完美了呢?其实不是,这种写法依旧存在问题,那就是指令重排问题。

上面new对象只有一行代码,然而这行代码在JVM底层却分成了3步:

  1. 分配内存来创建对象,即new操作。
  2. 创建一个对象lazyDoubleCheckSingleton此时lazyDoubleCheckSingleton==nul
  3. new出来的对象赋给lazyDoubleCheckSingleton

但实际运行的时候为了提升效率,这3步并不会按照实际顺序来运行。

假如线程t1进入同步代码块正在创建对象,而此时执行了后面2步,也即是此时lazyDoubleCheckSingleton依已经不为null了,但是对象却没有创建结束,这时候又来了一个线程t2进入getInstance方法,这时候if条件不再成立,线程t2会直接返回一个残缺不全的对象,自然会出现报错。

为了解决这个问题,下面引出了第四个单例版本,即

4. 内部类单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package ltd.waer.javabaseforio.PatternDesign.LazySingleton;
import java.io.Serializable;
import java.lang.reflect.Constructor;
@SuppressWarnings("all")
/**
 * @author: 八尺妖剑
 * @date: 2023/1/31 10:48
 * @email: ilikexff@gmail.com
 * @blog: https://www.waer.ltd
 * @Description: 内部类懒汉式单例-解决指令重排问题
 */
public class LazyInnerClassSingleton  implements Serializable {
    private LazyInnerClassSingleton () {

    };

    public static final LazyInnerClassSingleton getInstance() {
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY =new LazyInnerClassSingleton();
    }
}

这种写法巧妙的利用了内部类会等到外部调用时才会被初始化的特性,用饿汉式单例的思想实现了懒汉式单例。

这种写法看起来已经是高效完美,但其实存在安全隐患,比如可以通过反射的方式破坏这种写法,测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws Exception {
    Class<?> clazz = LazyInnerClassSingleton.class;
    Constructor constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true);
    Object o1 = constructor.newInstance();
    Object o2 = LazyInnerClassSingleton.getInstance();
    System.out.println(o1 == o2); //false
}

可以看到,虽然构造方法被私有化了,但是我们仍然可以利用反射来破坏单例。为了防止反射破坏单例,我们将上面的写法再改造一下。

5. 改进版的内部类单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

尽管如此,但假如我们的单例对象实现了 Serializable 接口,那么内部类的写法就还是能通过序列化来破坏

6. 实现了Serializable接口的内部类单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

由于实现了序列化的接口,所以内部类的写法依然可以通过序列化来进行破坏,比如使用下面这段测试代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.lazy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestLazyInnerClassSingleton2 {
    public static void main(String[] args) {
        //序列化攻击内部类式单例
        LazyInnerClassSingleton s1 = null;
        LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();

        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream("LazyInnerClassSingleton.text");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("LazyInnerClassSingleton.text");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (LazyInnerClassSingleton)ois.readObject();
            ois.close();
            System.out.println(s1 == s2);//输出:false
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

上面示例中 s1 是通过我们自己提供的全局入口创建的对象,而 s2 是通过序列化的方式创建的对象,不相等说明这是两个对象,也就是说序列化破坏了单例模式。

解决办法就是在 LazyInnerClassSingleton 类中加一个 readResolve 方法,防止序列化破坏单例。

7. 再改进版的内部类单例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.lazy;

import java.io.Serializable;

public class LazyInnerClassSingleton implements Serializable {

    private LazyInnerClassSingleton(){
        //防止反射破坏单例
         if(null != InnerLazy.LAZY){
           throw new RuntimeException("不允许通过反射类构造单例对象");
         }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return InnerLazy.LAZY;
    }

    private static class InnerLazy {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
    
    //防止通过序列化破坏单例
    private Object readResolve(){
        return InnerLazy.LAZY;
    }
}

这次返回了 true,也就是序列化没有破坏单例了。原因是因为 JDK 源码中在序列化的时候会检验一个类中是否存在一个 readResolve 方法,如果存在,则会放弃通过序列化产生的对象,而返回原本的对象。

这种方式虽然保证了单例,但是在校验是否存在 readResolve 方法前还是会产生一个对象,只不过这个对象会在发现类中存在 readResolve 方法后丢掉,然后返回原本的单例对象。这种写法只是保证了结果的唯一,但是过程中依然会被实例化多次,假如创建对象的频率增大,就意味着内存分配的开销也随之增大。

上面介绍了这么多种写法,看起来每种写法似乎都存在问题,难道就没有一种最优雅、安全、高效的方法吗?这就是我们最后要介绍的枚举式单例,不过在介绍枚举式单例之前,我们先刷一下副本,看看其它写法。

8. 注册式单例

将每一个实例都保存起来,然后在需要使用的时候直接通过唯一的标识获取实例,这便是注册式单例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton(){

    }

    private static Map<String,Object> ioc = new ConcurrentHashMap<>();

    public static Object getBean(String className){
        synchronized (ioc){
            //如果容器中不存在当前对象
            if(!ioc.containsKey(className)){
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    //将className作为唯一标识存入容器
                    ioc.put(className,obj);
                }catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            }
        }
        //如果容器中已经存在了单例对象,则直接返回
        return ioc.get(className);
    }
}

新建一个空对象 MyObject.java,用来测试单例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.register;

public class MyObject {
}

新建一个测试类 TestContainerSingleton.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.register;

public class TestContainerSingleton {
    public static void main(String[] args) {
        MyObject myObject1 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");
        MyObject myObject2 = (MyObject) ContainerSingleton.getBean("singleton.register.MyObject");

        System.out.println(myObject1 == myObject2);//输出:true
    }
}

上面返回 true 是因为我们加了 synchronized 关键字,实际上 Spring 框架中用的就是容器式单例,默认是线程不安全的。

9. 单线程安全单例

基于ThreadLocal实现,该单例不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,在单线程环境下线程天生安全。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.concurrent.ThreadFactory;
public class ThreadLocalSingleton {
    private ThreadLocalSingleton(){

    }

    private static final ThreadLocal<ThreadLocalSingleton> singleton = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };
    
    public static ThreadLocalSingleton getInstance(){
        return singleton.get();
    }
}

测试类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TestThreadLocalSingleton {
    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出
        System.out.println(ThreadLocalSingleton.getInstance());//主线程输出

        Thread t1 = new Thread(()->{
           ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + singleton);
        });
        t1.start();
    }
}

从上图可以看到,main 线程输出的和 t1 线程输出的并不是同一个对象,故而 ThreadLocal 式示例仅对单线程是安全的。

10. 枚举式单例

枚举式单例充分利用了枚举类的特性来创建单例对象,目前来说这是最优雅的一种写法。

照例我们新建一个空的对象 MyObject.java 来测试单例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.meiju;

public class MyObject {
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class EnumSingleton {
    INSTANCE;
    private MyObject myObject;

    EnumSingleton(){
        this.myObject = new MyObject();
    }

    public Object getData() {
        return myObject;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}
  • 新建测试类 TestEnumSingleton.java 进行测试。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package singleton.meiju;

public class TestEnumSingleton {
    public static void main(String[] args) throws Exception{
        EnumSingleton enumSingleton = EnumSingleton.getInstance();
        System.out.println(enumSingleton.getData() == enumSingleton.getData());//输出:true
    }
}

输出结果为 true,枚举式单例写法能有效的防止通过反射以及序列化手段的破坏,确实为目前最佳的单例实践之选。

小结

尽管实现单例模式的具体思想和方法多种多样,也各有千秋和不足,但在实际的使用中,并不是最优的就是最合适的,在使用单例模式时,应该结合具体的项目需求以及场景来选择合适的实现方式。比如小项目追求线程安全又拥有足够空间的情况下使用饿汉式单例又何尝不可?

致谢&引用:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023/01/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一个单例模式,被问7个问题,难!
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。单例模式属于创建型模式,它提供了一种创建对象的最佳方式。
田维常
2022/04/19
7980
一个单例模式,被问7个问题,难!
Java设计模式学习记录-单例模式
前言 已经介绍和学习了两个创建型模式了,今天来学习一下另一个非常常见的创建型模式,单例模式。 单例模式也被称为单件模式(或单体模式),主要作用是控制某个类型的实例数量是一个,而且只有一个。 单例模式 单例模式的实现方式  实现单例模式的方式有很多种,大体上可以划分为如下两种。 外部方式 在使用某些全局对象时,做一些“try-Use”的工作。就是如果要使用的这个全局对象不存在,就自己创建一个,把它放到全局的位置上;如果本来就有,则直接拿来使用。 内部实现方式 类型自己控制正常实例的数量,无论客户程序是否尝试过
纪莫
2018/07/04
3890
钻钻 “单例” 的牛角尖
上篇文章 走进 JDK 之 Enum 提到过,枚举很适合用来实现单例模式。实际上,在 Effective Java 中也提到过(果然英雄所见略同):
路遥TM
2021/08/31
4550
[设计模式]单例模式的n种写法
1.前言 写完这个题目,我感觉自己好像”孔乙己”啊,回字的四种写法要不要学啊~ 我们经常会用到单例模式,但是我对他一直没有一个统一的的认识,比如我清楚好多种单例的写法,但是每一种是怎么演化来的?具体解
呼延十
2019/07/01
5240
设计模式入门:单例模式
### UML类图 ![单例模式](http://upload-images.jianshu.io/upload_images/9709135-eba21220b6f018cd.jpg?imageMo
佛系贲八拉
2021/09/10
2170
设计模式入门:单例模式
Java 单例模式
在 2.6.2 中,我们读取了配置文件中的内容。假设我们把读入的配置文件封装成一个类:
星姮十织
2022/01/02
5990
设计模式入门:单例模式
  单例模式属于创建型模式,是一种较为简单的设计模式,但也是最容易让人犯错的。在不同的单例模式实现中,首先要确保构造函数是私有的,然后提供一个静态入口(方法)用于获取唯一的实例。   大多数情况下,不建议使用非线程安全的以及synchronized监视器锁实现的懒汉方式,在资源允许的情况下尽可能使用饿汉模式。如果明确要实现 lazy loading 效果时,可以使用静态内部类形式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用DCL双检锁的方式。
happyJared
2018/09/20
4380
设计模式入门:单例模式
设计模式-单例模式
单例模式是保证系统实例唯一性的重要手段。单例模式首先通过将类的实例化方法私有化来防止程序通过其他方式创建该类的实例,然后通过提供一个全局唯一获取该类实例的方法帮助用户获取类的实例,用户只需要也只能通过调用该方法获取类的实例。
别团等shy哥发育
2023/10/17
2910
【设计模式-单例模式】
今天来说一下同样属于创建型模式的单例模式,相信这个模式普遍都清楚,因为平时在编码的时候都会进行相应的使用,我这边就当做日志记录一下。免得以后忘了还得去搜,我发现我记忆里非常差,很多东西很快就忘记了,年纪大了没办法。
Liusy
2020/09/01
5550
【设计模式-单例模式】
单例模式的迭代式优化过程
在软件设计架构中,单例模式是最为常用的一种设计模式,所谓单例模式是指在创建某一个类的对象实例时该系统中有且仅有该类的一个实例,从而可以合理解决实例化对象的性能开销、资源分配等问题。从实现角度看,单例模式主要分为两种,一般称为饿汉式单例模式和懒汉式单例模式,以下逐一介绍
用户7506105
2021/08/09
3110
三、单例模式详解
2、单例模式是非常经典的高频面试题,希望通过面试单例彰显技术深度,顺利拿到Offer的人群。
编程之心
2020/08/12
9130
三、单例模式详解
单例模式的要点(写出一个单例模式)
(二)容器式单例模式代码及分析:(适用于实例非常多的情况,便于管理,但是是非线程安全的)
全栈程序员站长
2022/08/01
4570
【设计模式系列(二)】彻底搞懂单例模式
概念:单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
你好戴先生
2020/09/02
5140
当Kotlin邂逅设计模式之单例模式(一)
简述: 从这篇文章开始,我将带领大家一起来探讨一下Kotlin眼中的设计模式。说下为什么想着要开始这么一个系列文章。主要基于下面几点原因:
bennyhuo
2020/02/20
9640
设计模式系列:经典的单例模式
在小灰的知识星球里,有一位小伙伴分享了单例模式的知识,写得非常清晰详尽。小灰把这篇干货文章分享到公众号上,希望能够帮助到大家。
小灰
2023/09/02
1800
设计模式系列:经典的单例模式
设计模式 | 创建型 | 单例模式
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
被水淹没
2023/02/25
4460
设计模式 | 创建型 | 单例模式
摸鱼设计模式——单例模式
饿汉式单例,无论是否使用,都直接初始化。其缺点则是会浪费内存空间。因为假如整个实例都没有被使用,那么这个类依然会创建,这就白创建了。
摸鱼-Sitr
2021/01/04
6730
摸鱼设计模式——单例模式
聊一聊最难的设计模式 - 单例模式
很多人上来肯定一脸懵逼,因为在你的印象中,单例模式实现起来还是很简单的。不要着急,慢慢往下看,你就知道为什么我说它最难了。 1. 基本概念 单例模式是一种常用的创建型设计模式。单例模式保证类仅有一个实例,并提供一个全局访问点。 2. 适用场景 想确保任何情况下都绝对只有一个实例。 典型的场景有:windows 的任务管理器、windows 的回收站、线程池的设计等。 3. 单例模式的优缺点 优点 内存中只有一个实例,减少了内存开销。 可以避免对资源的多重占用。 设置全局访问点,严格控制访问
山海散人
2021/03/03
2890
聊一聊最难的设计模式 - 单例模式
1. 单例模式(Singleton)
有时候我们只需要一个类就可以完成所需要的业务了,那么就不需要多次创建对象、管理对象,因为这是一件十分耗费系统资源的事
晚上没宵夜
2022/05/09
2270
单例模式详解
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
孟君
2019/08/29
5610
单例模式详解
相关推荐
一个单例模式,被问7个问题,难!
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验