likes
comments
collection
share

23美团一面:双检锁单例会写吗?(总结所有单例模式写法)

作者站长头像
站长
· 阅读数 9

面试经历

(后来别人跟我说这种写法nacos里也常见)

记录一次面试经历

2023.06.01

美团海笔的,本来以为笔的情况也不咋好 看到牛客网上一堆ak说没面试机会的

结果也不知怎地到了一面

23美团一面:双检锁单例会写吗?(总结所有单例模式写法)

(略过自我介绍和项目介绍~)

面试官:会写单例吗,写个单例看看

我:

// 饿汉式
public class SingleObject {
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private
   private SingleObject(){}

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

面试官:嗯 你这个单例在没有引用的时候就创建了对象?优化一下

我:应该是懒汉模式!

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        // 多了一个判断是否为null的过程
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

面试官:你这个线程不安全啊 再优化一下?

我:那就加个锁吧

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

面试官:这种写法 多线程来了的话会阻塞在一起 能否再优化?

我:。。。 不会了

面试官:回去看看双检锁单例

···

之后问了数据库事务

  1. 读未提交(read uncommitted)
  2. 读已提交(read committed)
  3. 可重复读(repeatable read)
  4. 序列化(serializable)以及默认是哪个(repeatable read) 、

数据库的范式了解吗 等等

不出意外:

23美团一面:双检锁单例会写吗?(总结所有单例模式写法)

还是非常可惜的 这次机会 再加油吧

单例模式整理

学习自菜鸟教程

1.饿汉式

懒加载 no 多线程安全 yes 缺点:没有实现懒加载,即还未调用就创建了对象

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

2.懒汉式(线程不安全)

懒加载 yes 多线程安全 no

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

3.懒汉式(线程安全)

懒加载 yes 多线程安全 yes 缺点:和面试官说的那样,多线程访问会阻塞

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

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

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

这段代码是一个单例模式的实现,使用了双重检查锁定的方式来保证线程安全和性能。双重检查锁定是指在加锁前后都进行了一次判空的操作,以避免不必要的加锁操作。

而为了保证双重检查锁定的正确性,需要使用volatile关键字来修饰singleton变量,以禁止指令重排序优化。(JUC的知识串起来了!)如果没有volatile关键字修饰,可能会出现一个线程A执行了new Singleton()但是还没来得及赋值给singleton,而此时另一个线程B进入了第一个if判断,判断singleton不为null,于是直接返回了一个未初始化的实例,导致程序出错。

使用volatile关键字可以确保多线程环境下的可见性和有序性,即一个线程修改了singleton变量的值,其他线程能够立即看到最新值,并且编译器不会对其进行指令重排序优化。这样就能够保证双重检查锁定的正确性。

学到了 !!!

后来别人提醒:

23美团一面:双检锁单例会写吗?(总结所有单例模式写法)

23美团一面:双检锁单例会写吗?(总结所有单例模式写法)

(牛逼。。)

5.登记式/静态内部类

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

这段代码是一个单例模式的实现,使用了静态内部类的方式来保证线程安全和性能。静态内部类是指在外部类中定义一个静态的内部类,该内部类可以访问外部类的所有静态成员和方法,但是外部类不能访问内部类的成员和方法。

在这个单例模式的实现中,SingletonHolder就是一个静态内部类,它里面定义了一个静态的、final的、类型为Singleton的变量INSTANCE。由于静态内部类只有在被调用时才会被加载,因此INSTANCE对象也只有在getInstance()方法被调用时才会被初始化,从而实现了懒加载的效果。

由于静态内部类的加载是线程安全的,因此不需要加锁就可以保证线程安全。同时,由于INSTANCE是静态final类型的,因此保证了它只会被实例化一次,并且在多线程环境下也能正确地被发布和共享。

这种方式相对于双重检查锁定来说更加简单和安全,因此在实际开发中也比较常用。

6. 枚举

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

这段代码是使用枚举类型实现单例模式的一种方式。在Java中,枚举类型是天然的单例,因为枚举类型的每个枚举值都是唯一的,且在类加载时就已经被初始化。

在这个示例代码中,Singleton是一个枚举类型,其中只定义了一个枚举值INSTANCE。由于INSTANCE是一个枚举值,因此它在类加载时就已经被初始化,并且保证全局唯一。在使用时,可以通过Singleton.INSTANCE来获取该单例对象。

需要注意的是,虽然枚举类型天然的单例特性可以保证线程安全和反序列化安全,但是如果需要延迟初始化或者有其他特殊需求,仍然需要使用其他方式来实现单例模式。

7. 容器式单例

Java中可以使用容器来实现单例模式,比如使用Spring框架中的Bean容器。下面是一个使用Spring框架实现单例的示例代码:

  1. 定义一个单例类,比如MySingleton:
public class MySingleton {
    private static MySingleton instance;
    private MySingleton() {}
    public static MySingleton getInstance() {
        if (instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
    public void doSomething() {
        // do something
    }
}
  1. 在Spring的配置文件中定义该类的Bean:
<bean id="mySingleton" class="com.example.MySingleton" scope="singleton"/>
  1. 在Java代码中获取该Bean:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
MySingleton mySingleton = (MySingleton) context.getBean("mySingleton");
mySingleton.doSomething();

在上面的代码中,我们在Spring的配置文件中定义了一个名为mySingleton的Bean,它的类是com.example.MySingleton,作用域为singleton(即单例)。然后在Java代码中,我们通过ApplicationContext获取该Bean,并调用它的doSomething()方法。

使用Spring框架可以方便地管理单例对象,同时也可以很容易地实现依赖注入和控制反转等功能。

总结完成 继续加油!!!