李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
30.并发编程之单例模式安全习题
Leefs
2022-11-02 PM
1152℃
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
NLP
4
标签云
SpringCloud
HDFS
Golang
JavaSE
查找
Python
BurpSuite
字符串
ClickHouse
Yarn
Shiro
pytorch
正则表达式
SpringCloudAlibaba
数据结构和算法
算法
机器学习
MyBatis
Elastisearch
JavaWEB项目搭建
Quartz
GET和POST
Thymeleaf
MyBatisX
Linux
ajax
散列
数学
国产数据库改造
Spark RDD
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭