李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
30.并发编程之单例模式安全习题
Leefs
2022-11-02 PM
605℃
0条
[TOC] ### 一、balking模式习题 希望 doInit() 方法仅被调用一次,下面的实现是否有问题,为什么? ```java public class TestVolatile { volatile boolean initialized = false; void init() { if (initialized) { return; } doInit(); initialized = true; } private void doInit() { } } ``` **答**:有问题。 假设有两个线程t1,t2并行执行`init`方法,t1执行完`if (initialized)`时间片做出线程切换,t2也来执行`if (initialized)`,那么它俩对于条件的判断都是false,都会调用`doInit`方法,所以要把`init`放入同步代码块中,才能满足需求。 ### 二、线程安全单例习题 单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用 `getInstance`)时的线程安全,并思考注释中的问题 > 饿汉式:类加载就会导致该单实例对象被创建 > > 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建 #### 2.1 实现一 ```java // 问题1:为什么加 final // 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例 public final class Singleton implements Serializable { // 问题3:为什么设置为私有? 是否能防止反射创建新的实例? private Singleton() {} // 问题4:这样初始化是否能保证单例对象创建时的线程安全? private static final Singleton INSTANCE = new Singleton(); // 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由 public static Singleton getInstance() { return INSTANCE; } public Object readResolve() { return INSTANCE; } } ``` **问题1:为什么加 final?** - 单例模式,设计的初衷就为了只被加载一次; - 使用final,避免子类继承父类修改父类中的方法影响单例。 **问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例?** Java也可以通过反序列化创建一个对象,所以当你实现了序列化接口之后,可以通过反序列化来创建一个对象,从而影响单例,所以需要加上一个`readResolve`方法,当反序列化时,如果存在对象,就会返回已经创建好的对象`INSTANCE`。 ```java public Object readResolve(){ return INSTANCE; } ``` **问题3:为什么设置为私有? 是否能防止反射创建新的实例?** 设置为私有可以避免其他类调用其构造方法创建对象破坏单例,但是也不能避免反射来创建新的实例。 **问题4:这样初始化是否能保证单例对象创建时的线程安全?** 可以,由于静态成员变量的初始化是在jvm类加载时候完成,jvm会保证对象创建时的线程安全。 #### 2.2 实现二 ```java // 问题1:枚举单例是如何限制实例个数的 // 问题2:枚举单例在创建时是否有并发问题 // 问题3:枚举单例能否被反射破坏单例 // 问题4:枚举单例能否被反序列化破坏单例 // 问题5:枚举单例属于懒汉式还是饿汉式 // 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做 enum Singleton { INSTANCE; } ``` **问题1:枚举单例是如何限制实例个数的?** 其只有一个私有构造器,有一个静态初始化块,在类初始化阶段对枚举类的实例进行初始化,这些非常符合饿汉式单例模式的构造方式,静态的构造器在多线程的环境下也只能被执行一次;枚举通过静态方法的加载来满足限制实例个数为单例的目的。 **问题2:枚举单例在创建时是否有并发问题?** 枚举单例是静态成员变量,它是在类加载的时候创建的,线程安全性是由JVM保证的。 **问题3:枚举单例能否被反射破坏单例?** 不能,枚举时唯一一种可以保证单例不会被破坏的方式。 **问题4:枚举单例能否被反序列化破坏单例?** 枚举类都实现了序列化接口,已经考虑了在序列化或反序列化破坏单例的情况,不需要我们操作就可以保证单例不被破坏。 **问题5:枚举单例属于懒汉式还是饿汉式?** 由于枚举变量实际上就是静态成员变量,在类加载时就会完成创建,所以其实枚举单例属于是饿汉式。 **问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做?** 写一个构造方法,将初始化的逻辑写在构造方法中就可以了。 ```java public enum Singleton { /** * 单例 */ INSTANCE; void Singleton(){ // 初始化逻辑 } } ``` #### 2.3 实现三 ```java public final class Singleton { private Singleton() { } private static Singleton INSTANCE = null; // 分析这里的线程安全, 并说明有什么缺点 public static synchronized Singleton getInstance() { if( INSTANCE != null ){ return INSTANCE; } INSTANCE = new Singleton(); return INSTANCE; } } ``` **分析** 首先这个一定是线程安全的,因为这个synchronized是在static方法上加锁,就是对这个类对象加锁,锁的粒度是比较大的,多线程运行时,一定只有一个线程运行了`getInstance`方法,然后就会得到一个instance,之后才会解锁,之后的线程进入时就一定会拿到已经生成的INSTANCE,但是也存在缺点,那就是后面线程进入的时候,还会对这个类对象上锁,会降低运行效率。 #### 2.4 实现四 ```java public final class Singleton { private Singleton() { } // 问题1:解释为什么要加 volatile ? private static volatile Singleton INSTANCE = null; // 问题2:对比实现3, 说出这样做的意义 public static Singleton getInstance() { if (INSTANCE != null) { return INSTANCE; } synchronized (Singleton.class) { // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗 if (INSTANCE != null) { // t2 return INSTANCE; } INSTANCE = new Singleton(); return INSTANCE; } } } ``` **问题1:解释为什么要加 volatile ?** synchronized代码块中的程序可能会出现指令重排序,如果重排序先做了赋值操作,再调用构造方法,那么其他线程有可能拿到没有调用构造方法的INSTANCE引用。 **问题2:对比实现3, 说出这样做的意义?** 这样做,只有第一次访问时,才会进入 synchronized 的同步代码块,当后续线程访问 `getInstance()` 方法时,在第一次进行`if (INSTANCE != null)`判断就直接返回了,不会向实现3中的那样,每一次都要进入同步代码块,能够提升效率。 **问题3:为什么还要在这里加为空判断, 之前不是判断过了吗?** 这是为了避免多个线程第一次去创建INSTANCE对象出现并发问题。 #### 2.5 实现五 ```java public final class Singleton { private Singleton() { } // 问题1:属于懒汉式还是饿汉式 private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } // 问题2:在创建时是否有并发问题 public static Singleton getInstance() { return LazyHolder.INSTANCE; } } ``` **问题1:属于懒汉式还是饿汉式?** 属于懒汉式,因为类只会在第一次被用到时才会触发类加载操作,用到`getInstance`方法才会触发内部的类加载操作,没有执行类加载的话,静态内部类里面的静态变量也不会进行初始化操作。 **问题2:在创建时是否有并发问题?** 不会有并发问题, 类加载时对静态变量赋值时, 是由JVM进行赋值, 可以保证不会出现并发问题, 并且这种方式是比较推荐的实现单例的方式。
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2547.html
上一篇
29.并发编程之happens-before规则
下一篇
31.共享模型之无锁问题提出
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
Java阻塞队列
NIO
排序
容器深入研究
Spring
Hbase
JVM
递归
Java工具类
字符串
JavaSE
Spark
Zookeeper
Livy
Filter
Quartz
Azkaban
二叉树
栈
Beego
数据结构
国产数据库改造
DataWarehouse
nginx
Scala
Linux
哈希表
Shiro
Redis
SpringCloud
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞