单例模式,是Java中比较常见的一个设计模式,也是我在面试时经常会问到的一个问题。

经过我的初步统计,基本上有60%左右的人可以说出2-4种单例的实现方式,有40%左右的人可以说出5-6种单例的实现方式,只有20%左右的人能够说出7种单例的实现。

而只有不到1%的人能够说出7种以上的单例实现。

其实,作为面试官,我大多数情况下之所以问单例模式,是因为这个题目可以问到很多知识点。

比如线程安全、类加载机制、synchronized的原理、volatile的原理、指令重排与内存屏障、枚举的实现、反射与单例模式、序列化如何破坏单例、CAS、CAS的ABA问题、Threadlocal等知识。

一般情况下,只需要从单例开始问起,大概就可以完成一场面试的整个流程,把我想问的东西都问完,可以比较全面的了解一个面试者的水平。

以下,是一次面试现场的还原,从单例模式开始:

Q:你知道怎么不使用synchronized和lock实现一个线程安全的单例吗?

A:我知道,可以使用"静态内部类"实现。

静态内部类实现单例模式:

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

Q:除了静态内部类还会其他的方式吗?

A:还有就是两种饿汉模式。

饿汉实现单例模式:

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

饿汉变种实现单例模式:

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

Q:那你上面提到的几种都是线程安全的吗?

A:是线程安全的

Q:那是如何做到线程安全的呢?

A:应该是因为我使用了static,然后类加载的时候就线程安全了吧?

Q:其实你说的并不完全对,因为以上几种虽然没有直接使用synchronized,但是也是间接用到了。

(这里面根据回答情况会朝两个不同的方向展开:1、类加载机制、模块化等;2、继续深入问单例模式)

类加载过程的线程安全性保证

以上的静态内部类、饿汉等模式均是通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。

这其实是利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。

所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。

Q:那还回到刚开始的问题,你知道怎么不使用synchronized和lock实现一个线程安全的单例吗?

(并不是故意穷追不舍,而是希望能可以引发面试者的更多思考)

A:额、、、那枚举吧,枚举也可以实现单例。

枚举实现单例模式:

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

Q:那你知道枚举单例的原理吗?如何保证线程安全的呢?

枚举单例的线程安全问题

枚举其实底层是依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化的,和饿汉有点像, 所以他天然是线程安全的。

Q:所以,枚举其实也是借助了synchronized的,那你知道哪种方式可以完全不使用synchronized的吗?

A:en....我想想

Q:(过了一会他好像没有思路)你知道CAS吗?使用CAS可以实现单例吗?

(面试中,如果面试者对于锁比较了解的话,那我大多数情况下都会继续朝两个方向深入问:1、锁的实现原理;2、非锁,如CAS、ThreadLocal等)

A:哦,我知道,CAS是一项乐观锁技术,当多个线程尝试使用CAS同时更新一个变量时,只有其中一个线程能更新成功。

借助CAS(AtomicReference)实现单例模式:

public class Singleton {private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton() {}
public static Singleton getInstance() {for (;;) {            Singleton singleton = INSTANCE.get();if (null != singleton) {return singleton;            }singleton = new Singleton();if (INSTANCE.compareAndSet(null, singleton)) {return singleton;            }        }    }}

Q:使用CAS实现的单例有没有什么优缺点呀?

A:用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。

Q:你说的好像是优点?那缺点呢?

CAS实现的单例的缺点

CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。

另外,代码中,如果N个线程同时执行到 singleton = new Singleton();的时候,会有大量对象被创建,可能导致内存溢出。

Q:好的,除了使用CAS以外,你还知道有什么办法可以不使用synchronized实现单例吗?

A:这回真的不太知道了。

Q:(那我再提醒他一下吧)可以考虑下ThreadLocal,看看能不能实现?

(面试者没有思路的时候,我几乎都会先做一下提醒,实在没有思路再换下一个问题)

A:ThreadLocal?这也可以吗?

Q:你先说下你理解的ThreadLocal是什么吧

(通过他的回答,貌似对这个思路有些疑惑,不着急。先问一个简单的问题,让面试者放松一下,找找自信,然后再继续问)

ThreadLoacal

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。对于多线程资源共享的问题,同步机制(synchronized)采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。

同步机制仅提供一份变量,让不同的线程排队访问,而ThreadLocal为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Q:那理论上是不是可以使用ThreadLocal来实现单例呢?

A:应该也是可行的。

使用ThreadLocal实现单例模式:

public class Singleton {private static final ThreadLocal<Singleton> singleton =new ThreadLocal<Singleton>() {@Overrideprotected Singleton initialValue() {return new Singleton();         }     };public static Singleton getInstance() {return singleton.get();     }
private Singleton() {}}

Q:嗯嗯,好的,那有关单例模式的实现的问题我就问的差不多了。

(ThreadLocal这种写法主要是考察面试者对于ThreadLocal的理解,以及是否可以把知识活学活用,但是实际上,这种所谓的"单例",其实失去了单例的意义...)

(但是说实话,能回答到这一题的人很少,大多数面试者基本上在前面几道题就已经没有思路了,大多数情况下根本不会问到这个问题就要改方向了)

A:(心中窃喜)嗯嗯,学习到很多,感谢

Q:那...你知道如何破坏单例吗?

(单例问题,必问的一个。通过这个引申到序列化和反射的相关知识)

A:(额....)

长按关注,还原真实面试现场

面试官真是搞笑!让实现线程安全的单例,又不让使用synchronized!相关推荐

  1. 面试官: 谈谈什么是守护线程以及作用 ?

    来自:小哈学Java 目录 一.什么是守护线程 二.守护线程的作用及应用场景 三.总结 一.什么是守护线程 守护线程相对于正常线程来说,是比较特殊的一类线程,那么它特殊在哪里呢?别急,在了解它之前,我 ...

  2. 面试官:不使用synchronized和lock,如何实现一个线程安全的单例?

    单例,大家肯定都不陌生,这是Java中很重要的一个设计模式.稍微了解一点单例的朋友也都知道实现单例是要考虑并发问题的,一般情况下,我们都会使用synchronized来保证线程安全. 那么,如果有这样 ...

  3. 面试官让我讲下线程的WAITING状态,我笑了

    转载自  面试官让我讲下线程的WAITING状态,我笑了 面试官Q:你讲下线程状态中的WAITING状态,什么时候会处于这个状态?什么时候离开这个状态? 小菜J 会心一笑... 一个正在无限期等待另一 ...

  4. 面试官问:为什么 Java 线程没有 Running 状态?我懵了

    转载自 面试官问:为什么 Java 线程没有 Running 状态?我懵了 什么是 RUNNABLE? 与传统的ready状态的区别 与传统的running状态的区别 当I/O阻塞时 如何看待RUNN ...

  5. 面试官让我讲下线程的TIMED_WAITING状态,我又笑了

    转载自  面试官让我讲下线程的TIMED_WAITING状态,我又笑了 面试官Q:你讲下线程状态中的WAITING状态,什么时候会处于这个状态?什么时候离开这个状态? 小菜J 会心一笑,可以撮这里 - ...

  6. java线程池原理简答_面试官让我讲讲Java线程池的实现原理,我笑了...

    期待与你,一起进步 随着cpu核数越来越多,不可避免的利用多线程技术以充分利用其计算能力.所以,多线程技术是服务端开发人员必须掌握的技术. 线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,所以就 ...

  7. 面试官问:为什么 Java 线程没有Running状态?我懵了

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 title: 面 ...

  8. java singleton 多线程_Java创建线程安全的单例singleton

    Java创建线程安全的单例 单例的使用场景 JVM中仅需要一个实例,因此能节省内存,加快访问速度,比如数据库连接池,计数器等.Spring 中的Bean,默认也是单例的,共享资源的访问,比如日志文件, ...

  9. 使用static代码块实现线程安全的单例设计模式

    实现线程安全的单例设计模式的三种方式: DCL双检查锁机制实现线程安全 使用静态内置类实现线程安全 使用static代码块实现线程安全 -------------------------------- ...

最新文章

  1. Too many open files问题解决
  2. 《Windows 8 权威指南》——1.5 版本对比
  3. Android高级模糊技术RenderScript和FastBlur
  4. 是什么东西_隐形牙套附件是什么东西?
  5. oracle已经有了注释符再注释,关于oracle的注释位置
  6. Java多线程系列(六):深入详解Synchronized同步锁的底层实现
  7. Windows NAS迁移工具
  8. 计算机离散数学及其应用
  9. PPT实现单页点名的方式
  10. bios显存改8g rx_玩屏蔽?爆4GB显存版RX480可刷成8GB版
  11. Windows环境下用Anaconda(2.7/3.6)安装GPU版TensorFlow
  12. iOS网络协议_HTTP/TCP/IP浅析
  13. 跑跑卡丁车Bingo喜当托儿纪念,2022/04/30,23:38:56
  14. 清华数为DWF低代码平台使用感悟
  15. 用aspose转换文档成PDF导致中文变成方框
  16. Metrics 入门教程
  17. 补淘宝单平台哪个便宜?如何补单才能增加权重?
  18. Selenium学习 - 简介
  19. __init__() takes 1 positional argument but 5 positional arguments (and 1 keyword-only argument) were
  20. JEECG 新手常见问题大全,入门必读

热门文章

  1. mysql 1045错误ODBC_MySQL ERROR 1045 (28000) 错误的解决办法
  2. excel统计行数_百万到亿级数据,快速统计查询
  3. android 抓log暗码,「有用功」强大的安卓暗码命令 你都知道吗?
  4. 查看pem证书的ASN数据结构的方法
  5. 一个java中HashMap和HashSet的应用实例
  6. NDR(网络威胁检测及响应)与NTA的区别(网络流量检测)
  7. Coremail邮件系统存在配置信息泄露漏洞(CNVD-2019-16798)
  8. mysql:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
  9. shell获取路径文件的文件名
  10. maven打包不用eclipse插件