《JUC并发编程 - 高级篇》04 -共享模型之内存 (Java内存模型 | 可见性 | 有序性 )(下)

简介: 《JUC并发编程 - 高级篇》04 -共享模型之内存 (Java内存模型 | 可见性 | 有序性 )

5.4 习题

5.4.1 balking 模式习题

希望 doInit() 方法仅被调用一次,下面的实现是否有问题,为什么?

public class TestVolatile {
    boolean initialized = false;
    public void init() {
        synchronized(this){
            if (initialized) {//t2
              return;
           }
            doInit();//t1
            initialized = true;
        }   
    }
    private void doInit() {
    }
}

有问题,对initialized的读和写 多处出现。当t1在执行doInit()方法时,此时t2在第四行判断通过后也会执行doInit()方法,最终会导致doInit()被执行两次。

**解决办法:**参考同步模式之Balking

952e2914f5e9471cc9619e8d293e9613.png

volitile适用于一个线程写多个线程读的情况,也可用于double-checked中synchronized外指令重排序问题


5.4.2 线程安全单例习题


单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用getInstance)时的线程安全,并思考注释中的问题


  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

实现1:饿汉式

/**
 * 问题1:为什么加 final?
 *       为了防止子类中的一些方法覆盖父类的提供单例的方法,从而破坏父类的单例
 * 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
 *       实现了序列化,就可以采用网络流的形式传输Java对象,最终可以通过反序列化创建对象。这个对象和单例模式创建的对象是不同的对象
 *       ,也就是破坏了单例。
 *       解决办法:加一个public Object readResolve(){...}
 *       原因:在通过反序列化创建对象的过程中,如果发现readResolve()有返回值,则直接使用该方法,不再用反序列化的字节码来创建对象。
 *
 * 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
 *       如果为共有的话,也就可以直接new()无限的创建对象,也就不是单例了。
 *       不能,暴力反射依旧可以创建实例对象
 *
 * 问题4:这样初始化是否能保证单例对象创建时的线程安全?
 *       静态成员变量的赋值操作是在类加载阶段完成的,所以可以保证
 *
 *
 *  问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
 *        ① 使用方法,可以有更好的封装性,也可以改进成懒惰初始化。
 *        ② 创建单例对象时可以有更多的控制
 *        ③ 可以对泛型进行支持
 *
 */
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;
    }
}

实现2:枚举类实现单例

/**
 * 问题1:枚举单例是如何限制实例个数的
 *      通过观察源码,可以发现枚举本质是一个静态成员变量,然后通过static{}
 *      进行初始化工作
 * 问题2:枚举单例在创建时是否有并发问题
 *       静态成员变量的赋值操作是在类加载阶段完成的,所以可以保证
 * 问题3:枚举单例能否被反射破坏单例
 *        不能,枚举类型不能通过newInstance反射。
 * 问题4:枚举单例能否被反序列化破坏单例
 *        不能,因为ENUM父类中的反序列化是通过valueOf实现的 不是通过反射
 * 问题5:枚举单例属于懒汉式还是饿汉式
 *        饿汉式
 * 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
 *        可参考:https://blog.csdn.net/qq_39714944/article/details/91973832
 * 注意:关于为啥强烈建议使用枚举类来实现单例模式的原因可参考:
 *  https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==
 *  &mid=2650121482&idx=1&sn=e5b86797244d8879bbe9a69fb72641b5
 *  &chksm=f36bb82bc41c313d739f485383d3a868a79020c995ee86daef
 *  026a589f4782916c42a8d3f6c7&mpshare=1&scene=1&srcid=0614J9
 *  OX5zkoAnHiPYX2sHiH#rd
 */
//无参构造的枚举类
enum Singleton {
    INSTANCE;
}
//有参构造的枚举类
enum Singleton {
    INSTANCE(0,"INSTANCE");
    int key;
    String value;
    //构造成员方法默认都是private,不能通过new的方式创建对象
    Singleton(int key, String value) {
        this.key = key;
        this.value = value;
    }
    public int getKey() {
        return key;
    }
    public void setKey(int key) {
        this.key = key;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}

实现3:懒汉式

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;
    }
}

实现4:DCL ,本质就是对懒汉式的改进

  • 缩小了synchronized的范围
  • 但也带来了其他问题,比如指令可能重排序,可能重复创建对象。可通过双层判断 + volatile 解决
public final class Singleton {
    private Singleton() { }
    // 问题1:解释为什么要加 volatile ?
    // 为了防止指令的重排序。INSTANCE = new Singleton(); 对t1当发生重排序时候可能先进行
    // 赋值操作,此时还没有进行实例化,这时候t2进来后 外部if (INSTANCE != null) 不为空,直接返回了INSTANCE,但是这个是还未初始化的实例
    // 存在一定的问题。
    //加上volatile后,就不会出现指令重排序了。要么当t2进行 外部if (INSTANCE != null) 时,已经初始化赋值完毕。要么t2进行if (INSTANCE != null)
    //时,此时INSTANCE为null,直接继续向下运行... 就不会出现问题了。
    private static volatile Singleton INSTANCE = null;
    // 问题2:对比实现3, 说出这样做的意义
    //缩小了锁的范围,第一次需要进入synchronized代码块,之后就不需要进入synchronized同步了。
    public static Singleton getInstance() {
        if (INSTANCE != null) {
            return INSTANCE;
        }
        synchronized (Singleton.class) {
            // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗
            //为了防止,第一次并发访问时,对象不要重复创建。比如t1正在 new 创建对象,此时t2在synchronized外等待,当t1
            //t1创建完成并走出synchronized时,t2也会进入synchronized块,所以需要再次进行判断一下,防止重复创建。
            if (INSTANCE != null) { // t2
                return INSTANCE;
            }
            INSTANCE = new Singleton();
            return INSTANCE;
        }
    }
}

实现5:内部类的方式

public final class Singleton {
    private Singleton() { }
    // 问题1:属于懒汉式还是饿汉式
    // 类加载本身就是懒惰的,第一次使用时才进行加载。
    // 如果仅仅创建是使用外部的Singleton,没有使用getInstance(),
    // 就不会触发LazyHolder的类加载,也就不会进行静态变量的初始化操作。
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    // 问题2:在创建时是否有并发问题
    // 无 静态成员变量在类加载时进行初始化操作
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

本章小结


本章重点讲解了 JMM 中的


可见性 - 由 JVM 缓存优化引起

有序性 - 由 JVM 指令重排序优化引起

happens-before 规则

原理方面

CPU 指令并行

volatile

模式方面

两阶段终止模式的 volatile 改进

同步模式之 balking



相关文章
|
11月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1202 3
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
10月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
311 4
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
417 0
|
10月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
11月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1102 17
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
685 2
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
377 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
674 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化