文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《尼恩Java面试宝典 最新版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
单例模式是Java的核心模式,最好人人都精通。
那么,该如何优雅的使用单例模式呢?
来看看:
另外,也看看 美团是如何进行 单例模式 的面试的。下面是一个美团面试题:
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:语雀 或者 码云
优点:
缺点:
private static boolean flag = false;
private Singleton() {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public static void main(String[] args) {
}
(主要使用懒汉和懒汉式)
1.饿汉式:
类初始化时,会立即加载该对象,线程天生安全,调用效率高。
2.懒汉式:
类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
3.静态内部方式:
结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
4.枚举单例:
使用枚举实现单例模式
优点: 实现简单、调用效率高,枚举本身就是单例, 由jvm从根本上提供保障!避免通过反射和反序列化的漏洞;
缺点: 没有延迟加载。
5.双重检测锁方式
因为JVM重排序、内存可见性的原因,可能会初始化多次,
所以: 需要通过 Double Check 双重检查+ synchronized + Volatile 解决 同步问题和可见性问题。
类初始化时,会立即加载该对象,线程天生安全,调用效率高。
package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {
// 类初始化时,会立即加载该对象,线程安全,调用效率高
private static final FSingleton instance = new FSingleton();
// 私有化构造方法
private FSingleton() {
}
public static FSingleton getInstance() {
return instance;
}
}
饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
特点:
优点:
缺点:
类初始化时,不会初始化该对象,
真正需要使用的时候,才会创建该对象,具备懒加载功能。
package com.crazymakercircle.designmodel.singleton;
//懒汉模式
public class FLazySingleton {
//类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
private static FLazySingleton instance = null;
// 私有化构造方法
private FLazySingleton() {
}
//真正需要使用的时候才会创建该对象
public static synchronized FLazySingleton getInstance() {
if(null==instance)
{
instance=new FLazySingleton();
}
return instance;
}
}
静态内部方式:
结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
package com.crazymakercircle.designmodel.singleton;
public class Singleton {
//静态内部类
private static class LazyHolder {
//通过final保障初始化时的线程安全
private static final Singleton INSTANCE = new Singleton();
}
//私有的构造器
private Singleton (){}
//获取单例的方法
public static final Singleton getInstance() {
//返回内部类的静态、最终成员
return LazyHolder.INSTANCE;
}
}
枚举单例:
使用枚举实现单例模式 优点:实现简单、调用效率高,
枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。
package com.lijie;
package com.crazymakercircle.designmodel.singleton;
//饿汉式
public enum SingletonEnumStyle {
INSTANCE;
// 类初始化时,会立即加载该对象,线程安全,调用效率高
public static SingletonEnumStyle getInstance() {
return INSTANCE;
}
}
枚举实现单例模式 优点:
缺点:
所谓懒加载,就是直到第一次被调用时才加载。其实现需要考虑并发问题和指令重排,代码如下:
public class Singleton {
private volatile static Singleton instance; //①
private Singleton() { //②
}
public static Singleton getInstance() {
if (instance == null) {//③
synchronized (Singleton.class) {
if (instance == null) {//④
instance = new Singleton();//⑤
}
}
}
return instance;
}
}
这段代码精简至极,没有一个字符是多余的,下面逐行解读一下:
首先,注意到①处的volatile关键字,它具备两项特性:
一是保证此变量对于所有线程的可见性。
即当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
二是禁止指令重排序优化。
这里解释一下指令重排序优化:
代码 ⑤ 处的instance = new Singleton(); 并不是原子的,大体可分为如下 3 步:
JVM 允许在保证结果正确的前提下进行指令重排序优化。
即如上 3 步可能的顺序为1->2->3 或 1->3->2 。
如果顺序是 1->3->2 ,当 3 执行完,2 还未执行时,另一个线程执行到代码 ③ 处,发现instance不为null,直接返回还未初始化好的instance并使用,就会报错。
所以使用volatile,就是为了保证线程间的可见性和防止指令重排。
其次,代码②处将构造函数声明为private目的:在于阻止使用new Singleton()这样的代码生成新实例。
最后,当客户端调用Singleton.getInstance()时,先检查是否已经实例化(代码③),未实例化时同步代码块,然后再次检查是否已实例化(代码④),然后才执行代码⑤。
两次检查的意义在于,防止synchronized同步过程中其他线程进行了实例化。
这就是著名的双重检查锁(Double check lock)实现单例,也即懒加载。
TIPS:
网上也有直接对 getInstance()方法加锁的版本,这样大范围的方法级别加锁会导致并发变低,实际上第一次调用生成实例之后,后续获取实例根本不需要并发控制了。
而本例的双重检查锁版本可以避免此并发问题。
双重检测锁 单例 非常重要, 涉及到Volatile 和可见性的底层原理, 深入学习/系统学习 双重检测锁 单例的内容, 请参见 《Java 高并发核心编程 卷2》 第8.1节:线程安全的单例模式
答案是:枚举单例
并且,单例的名称叫做 INSTANCE
通过这个 INSTANCE 名字 做 关键词搜索, 能搜到一大把
来一个案例
再来一个案例
缓存之王 Caffeine 的详细资料,请参考下面的博客、或者对应的PDF文件:
答案是:枚举单例
并且,单例的名称叫做 INSTANCE
单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
明确定义后,看一下代码:
package com.crazymakercircle.designmodel.singleton;
//饿汉式
public class FSingleton {
// 类初始化时,会立即加载该对象,线程安全,调用效率高
private static final FSingleton instance = new FSingleton();
// 私有化构造方法
private FSingleton() {
}
public static FSingleton getInstance() {
return instance;
}
}
饿汉模式就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
特点:
优点:
缺点:
public class Singleton {
private volatile static Singleton instance; //①
private Singleton() { //②
}
public static Singleton getInstance() {
if (instance == null) {//③
synchronized (Singleton.class) {
if (instance == null) {//④
instance = new Singleton();//⑤
}
}
}
return instance;
}
}
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
特点:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,需要通过多种手段,保证线程安全和内存可见性:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
1、一个党只能有一个主席。
2、Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用 Double Check 双重检查锁,synchronized (Singleton.class) 防止多线程同时进入造成instance 被多次实例化。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从下面的链接获取:语雀 或者 码云
《尼恩Java面试宝典》
《Springcloud gateway 底层原理、核心实战 (史上最全)》
《Flux、Mono、Reactor 实战(史上最全)》
《sentinel (史上最全)》
《Nacos (史上最全)》
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《clickhouse 超底层原理 + 高可用实操 (史上最全)》
《redis 集群 实操 (史上最全、5w字长文)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
《红黑树( 图解 + 秒懂 + 史上最全)》
《分布式事务 (秒懂)》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《缓存之王:Caffeine 的使用(史上最全)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》
《Docker原理(图解+秒懂+史上最全)》
《Redis分布式锁(图解 - 秒懂 - 史上最全)》
《Zookeeper 分布式锁 - 图解 - 秒懂》
《Zookeeper Curator 事件监听 - 10分钟看懂》
《Netty 粘包 拆包 | 史上最全解读》
《Netty 100万级高并发服务器配置》
《Springcloud 高并发 配置 (一文全懂)》