注册

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

面试经历


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


记录一次面试经历


2023.06.01


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


结果也不知怎地到了一面


image.png


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


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


我:


// 饿汉式
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) 、

数据库的范式了解吗 等等


不出意外:


image.png


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


单例模式整理


学习自菜鸟教程


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 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变量的值,其他线程能够立即看到最新值,并且编译器不会对其进行指令重排序优化。这样就能够保证双重检查锁定的正确性。


学到了 !!!


后来别人提醒:


image.png


image.png


(牛逼。。)


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框架可以方便地管理单例对象,同时也可以很容易地实现依赖注入和控制反转等功能。


总结完成 继续加油!!!


作者:ovO
来源:juejin.cn/post/7244820297290465317

0 个评论

要回复文章请先登录注册