Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • JAVA基础

  • 集合容器

  • Netty

  • JVM

  • JUC

    • 并发机制初识
    • JMM语义与重排
      • 1.内存模型基础
      • 2.重排序
        • as-if-serial语义
      • 3.volatile内存语义
      • 4.锁的内存语义
      • 5.final内存语义
      • 6.happens-before关系
      • 7.延迟初始化方案
        • Double-Checked Locking存在的问题
    • 多线程通信与编程应用
    • Lock并发锁原理
    • 并发容器与框架
    • 原子操作类
    • 并发工具类
    • 线程池
    • Executor框架
    • 并发编程实践
    • JUC面试
  • Java
  • JUC
phan
2023-10-10
目录

JMM语义与重排

# JMM语义与重排

# 1.内存模型基础

顺序一致性模型(内存屏障限制最多)+重排序并行优化=JMM

  • Java并发采用共享内存模型,线程之间的通信是根据写-读内存中的公共状态进行隐式通信。
  • Java源程序在编译执行的过程,可能会经历几种重排序。JMM通过插入内存屏障指令(禁止前后指令重排),保证一致的内存可见性。

🔥无论是哪种内存模型,添加的内存屏障和约束越少,那么指令的并行度越高,性能越好;但是相对的指令代码执行的顺序不可控,易编程性变差。

# 2.重排序

代码在执行时,编译器和处理器可能都会对其进行重排序。

JMM会制定相应的规则,限制不同情况下的重排。

结论:多线程下,对数据依赖和控制依赖的操作进行重排序会改变执行结果。

# as-if-serial语义

保证单线程执行结果不会改变。换句话说,编译器和处理器重排时,不能改变存在数据依赖关系的操作。(单线程下重排控制依赖操作不受影响)

# 3.volatile内存语义

volatile具有以下特性和语义:

  • 可见性:其它线程总能读到volatile变量最新的值
  • 原子性:对单个volatile变量的操作具有原子性。每次读/写操作等效于使用一个synchronized锁同步。
  • volatile写:在线程内存空间对当前volatile变量修改后,刷新到主内存共享区域。
  • volatile读:线程内存空间的volatile变量置为无效,从主内存读取最新共享变量。

结论:为了实现volatile内存语义,通过插入不同内存屏障实现代码的同步。

# 4.锁的内存语义

ReentrantLock分为公平锁和非公平锁,它们的锁获取-释放的区别如下,分别代表两种锁的实现:

  • 公平锁锁获取时,会读volatile变量;释放锁时会写volatile变量。——利用volatile的内存语义。
  • 非公平锁在获取锁时,会使用CAS方法更新state值。

# 5.final内存语义

final域重排序与内存屏障限制:

  • 写操作:final变量的写只能在构造函数内进行,不能重排到构造函数之外。
  • 读操作:①final变量的对象引用的读操作②final变量的读操作。两者执行顺序不能改变

# 6.happens-before关系

happens-before

描述代码编写的先后关系,强调前面的操作结果一定对后面操作结果可见。

代码之间只要不存在数据依赖关系,那么代码实际执行顺序并不一定跟happens-before关系保持一致。

常见的happens-before规则:

  • 程序顺序规则
  • volatile规则:volatile变量写 —> happens-before —> volatile变量读。保证所有线程能够读到最新的共享变量。
  • 监视器锁规则:对一个锁的解锁—>happens-before—>对一个锁的加锁。解锁后锁的状态要对全局可见,才能够加锁。
  • 传递性
  • start规则:A线程执行ThreadB.start启动线程,那么在执行ThreadB.start()之前对共享变量所做的修改,接下来在B线程开始执行后都是可见的。
  • join规则:A线程执行ThreadB.join终止B线程,那么B线程终止前修改的共享变量,在A执行ThreadB.join都能够读取到。

# 7.延迟初始化方案

延迟初始化:Java程序中,有时候对象初始化的比较大,需要在使用该对象的时候再进行初始化,也就是懒加载,因此程序员需要延迟初始化。

public static Instance getInstance(){
	if(instance==null)
		instance=new Instance();
     return instance;
}
1
2
3
4
5

但上述代码在多线程的环境下存在问题。

# Double-Checked Locking存在的问题

上述代码在多线程下存在问题,如果在方法加锁synchronized,虽然问题可以解决,但是加锁开销较大。

我们希望如果当前对象已经初始化,那么直接返回对象,不需要重新获取锁资源,从而减小锁粒度和开销,因此就有了下面的代码:

public class DoubleCheckedLocking { 						// 1
     private static Instance instance; 						// 2
     public static Instance getInstance() { 				// 3
         if (instance == null) { 							// 4:第一次检查
             synchronized (DoubleCheckedLocking.class) { 	// 5:加锁
                 if (instance == null) 						// 6:第二次检查
                 instance = new Instance(); 				// 7:问题的根源出在这里
             } 												// 8
         } 													// 9
         return instance; 									// 10
     } 														// 11
}
1
2
3
4
5
6
7
8
9
10
11
12

然而上述代码存在的问题在于,第七行代码并不是一个原子操作,它分为两个步骤①分配内存地址空间,并完成初始化②instance引用指向内存空间。在多线程下若发生重排,则线程B在执行第4行进行判断时,会出现instance不为null,但是还没完成初始化的情况。

解决方案分为两个:

  1. ✨保证instance=new Instance()初始化过程中,不出现重排。
  • 直接将instance变量设置为volatile修饰。单个volatile操作具有原子性。
  1. ✨instance=new Instance()初始化的过程对外不可见。

    • 基于类初始化的方案:在执行类初始化期间,JVM会去获取一个锁,它可以同步多个线程同步过程,主要是类初始化锁的五个阶段...
    public class InstanceFactory {
         private static class InstanceHolder {
             public static Instance instance = new Instance();
         }
         public static Instance getInstance() {
             return InstanceHolder.instance ;  //这里将导致InstanceHolder类被初始化
         }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
编辑 (opens new window)
#JUC
上次更新: 2023/12/15, 15:49:57
并发机制初识
多线程通信与编程应用

← 并发机制初识 多线程通信与编程应用→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式