-
Notifications
You must be signed in to change notification settings - Fork 116
单例模式
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton singleton;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (singleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (singleton == null) {
singleton = new DoubleCheckSingleton();
}
}
}
return singleton;
}
}
- 第一次判空 在同步代码块外部进行判断,在单例已经创建的情况下,避免进入同步代码块,提升效率
- 第二次判空 为了避免创建多个单例。假设线程1首先通过第一次判空,还未获得锁时时间片就用完了,此时,线程2获得CPU时间片,并调用单单例方法,此时singleton仍然为空,于是线程2顺利创建了singleton对象。稍后,线程1获得时间片,由于已经执行过了第一层判空,此时如果没有第二次判空,那么线程1也会再创建一个singleton实例,即不满足单例的要求。所以第二此判空很有必要。
编译器为了优化程序性能,可能会在编译时对字节码指令进行重排序。重排序后的指令在单线程中运行时没有问题的,但是如果在多线程中,重排序后的代码则可能会出现问题。
双重锁校验中实例化singleton的代码在编译成字节指令后并不是一个原子操作,而是会分为三个指令:
- 1.分配对象内存:memory = allocate();
- 2.初始化对象:instance(memory);
- 3.instance指向刚分配的内存地址:instance = memory;
但是由于编译器指令重排序,上述指令可能会出现以下顺序:
- 1.分配对象内存:memory = allocate();
- 2.instance指向刚分配的内存地址:instance = memory;
- 3.初始化对象:instance(memory);
假设这个singleton的实例化经过了编译器的重排序,此时有一个线程1调用了该单例方法,并在执行完上述第2步后用完了CPU事件片,此时singleton对象实际上还没有被初始化,但是singleton却被赋值指向了第一步分配的内存。此时,一个线程2获得CPU时间片,并调用这个单例方法,发现singleton不为null,随即return了singleton,但singleton实际上并没有初始化,因此可能造成程执行序异常。
如果给成员变量加了volatile关键字,那么编译器便不会对其进行指令重排序,也就不会出现上边的问题。
既能保证线程安全,又能实现延迟加载。缺点时使用synchronized关键字会影响性能。
public class StaticSingleton {
private StaticSingleton singleton;
private StaticSingleton() {
}
private static class SingletonHolder {
public static StaticSingleton INSTANCE = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种单例利用了类加载的特性,在《Java虚拟机规范》中对于类初始化的时机有着严格的约束:
① 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
② 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
③ 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
④ 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
⑤ 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
⑥ 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
从上述类初始化的条件可以看出,如果不调用SingletonHolder.INSTANCE,SingletonHolder类就不会被加载到虚拟机,SingletonHolder不被加载到虚拟机那么INSTANCE实例也不会被实例化。
而当调用了getInstance后会调用SingletonHolder.INSTANCE,这里是一个getstatic指令,所有会触发SingleHolder的初始化,初始化前会进行SingletonHolder的类加载,在类加载的初始化过程中INSTANCE会被实例化。
// 枚举单例
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("通过枚举单利打印日志...");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
EnumSingleton.INSTANCE.doSomething();
}
}
枚举实现的单利是最完美的一种方式。这种方式可以防止序列化与反序列化造成创建多个实例的问题,而前面的几种方式都无法解决这个问题。
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- AQS的实现原理
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Binder与AIDL
- Binder实现原理
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- ActivityManagerService
- APP启动流程
- PMS安装与签名校验
- Dalvik与ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方