并发编程_jmm部分

文章探讨了Java内存模型(JMM)如何解决并发编程中的可见性、有序性和原子性问题,通过关键字volatile、synchronized和CAS实现线程安全。volatile确保变量在多线程环境中的可见性和有序性,而synchronized提供锁机制保证同步。此外,还介绍了AQS(AbstractQueuedSynchronizer)和反射在并发控制中的应用。
摘要由CSDN通过智能技术生成

1. JMM 理解

前提:并发编程有3大问题,可见性、有序性、原子性。
导致可见性的原因是缓存,有序性的原因是 编译器优化。解决方法就是直接禁用缓存和编译器优化,导致程序性能堪忧。
因此合理的方案就是按需禁用缓存和编译器优化。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

所以: java 内存模型针对在多线程环境下,可见性 有序性制定了一些规范,在jvm 层面 提供按需禁用缓存和编译优化的方法。具体就是使用synchronized,volatile,final三个关键字和Happens-before 规则。 解决并发编程中 的代码的有序性和可见性。

long double 32位原子性问题。

3
如果需要保证 long 和 double 在 32 位系统中原子性,需要用 volatile 修饰

load load 两个连续的读不要重排序 防止读跑上去

防止 B 的 Load 重排到 A 的 Load 之前

if(A) {
    LoadLoad
    return B
}

read(A)
LoadLoad
read(B)
  • 意义:A == true 时,再去获取 B,否则可能会由于重排导致 B 的值相对于 A 是过期的
    3

loadstore 防止连续 读写 防止写跑上去。

3

Acquire 第一个是load,防止后续的 读、写 不要重排序

同一线程内,读 操作之后的的读写,上不去,同时第一个load能读到主存中的最新值。
3

store store 连续的写 防止写跑下来

  • 防止 A 的 Store 被重排到 B 的 Store 之后
A = x
StoreStore
B = true
  • 意义:在 B 修改为 true 之前,其它线程别想看到 A 的修改
    • 有点类似于 sql 中更新后,commit 之前,其它事务不能看到这些更新(B 的赋值会触发 commit 并撤除屏障)
      3

release 防止读和写 跑下来。 ss ls

同一线程内,写操作之前的 , 读写下不来,后面的store 都能将改动的 都写入主存。
store store
load sore
防止上面的 读操作 写操作,被重排序到 写操作下面
3

store load ** 发生在线程切换时有效。 保证可见性。

sotre load 屏障 在线程切换时,保证可见性。

意义:屏障前的改动都同步到主存,屏障后的 Load 获取主存最新数据

  • 防止屏障前所有的写操作,被重排序到屏障后的任何的读操作,可以认为此 store -> load 是连续的
  • 有点类似于 git 中先 commit,再远程 poll,而且这个动作是原子的
    3

2.volatile 本质

写变量时 加 loadstore store store 屏障 和 store load 屏障

红色 蓝色是两个线程。
a

读取变量时 load load ,load store

在变量加入load load load store 屏障
3

1. 保证单一变量赋值的原子性

32 位操作系统,long double

2. 保证变量的有序性

线程内通过内存屏障保证有序,线程切换按照happen-before 有序。

partial ordering

total ordering

3. 可见性

线程切换时 ,发生了写->读,则变量可见,顺便影响普通变量可见性。
在volatile 变量 加入store load 屏障。
红色线程的写入同步到主存,然后让蓝色线程的读取,取主存中读取最新值。
3

3. Synchronization order

线程内部的一个顺序

4.happen-before 原则

在线程切换时,代码的顺序 和可见性。

表达的是,前一个线程的操作的结果 对后续线程的操作是可见的。

线程启动 和运行边界

线程1 启动线程2前对共享变量进行修改,在线程2运行时,读取共享变量一定能看到修改。
3

线程的结束 和join 的边界。

线程1 结束前,对共享变量的修改。t2线程等待t1线程的解释 join,t2也能读取到共享变量的读取。

线程的打断 和得知线程的打断。

interrupt
t1线程修改共享变量,t1线程对t2线程打断,t2线程得知打断,能够读取到共享变量的改变。
3

unlock 和lock 边界。

t1线程解锁前对共享变量的修改,t2线程加锁后,能够读取到共享变量的修改。

volatile 对变量的写 和volatile 的读 的边界

传递性

如果a hb b,
b hb c,
那么有a hb c

5. cas 原理

使用乐观锁机制,保证变量读写的原子性。
volatile+ cas 实现原子 可见 有序
volatile 搭配cas 保证 共享变量的可见性。
线程数小于cpu 核心数,乐观锁性能高。

aqs 的state 使用volatile

cas + volatile 保证 state 的最新值 和 互斥。

concurrenthashmap 懒惰初始化

有一个sizectl 属性,volatile 修饰,保证可见性。
在第一次 put 时,检查sizectl,使用cas 把 0 改成 -1, (-1,hash表正在被初始化),
其他线程来 初始化 cas 失败,不会重复创建了。

6. synchronized

有序性

3
在monitor enter 和 monitor exit 加了相应的屏障,
保证了 同步代码块 内部的代码,共享变量的读写, 不会重排序到同步代码块的外面。
强调 :
但是 在同步代码块内部还是会存在重排序的。

7 反射

反射可见 对象自己的 类名、接口、父类、成员变量、方法。
类加载 收集 类的信息,记录在instance klass中

反射是直接获取加载了方法区的类模板,所以可以拿到整个类的所有东西,看似是打破了封装特性,但实际上反射的真正作用是为了解偶以及实现动态性
3

8 cas

CAS(Compare and Swap)是一种并发控制机制,用于解决多线程环境下的原子性操作问题。它主要包含以下步骤:

比较:首先,CAS操作会比较某个内存位置的当前值与预期值是否相等。

交换如果当前值与预期值相等CAS操作会将新值写入该内存位置。否则,表示其他线程已经修改了该内存位置的值,CAS操作失败。

返回结果:CAS操作返回一个标识,指示操作是否成功。

CAS操作是原子的,即在执行过程中不会被其他线程中断,并且能够确保操作的原子性。因此,CAS通常用于实现线程同步、无锁算法以及乐观锁等。

在实现上,CAS依赖底层硬件提供的原子指令,如x86处理器上的CMPXCHG指令。通过这些原子指令,CAS可以在一个操作中同时进行比较和交换操作,避免了传统锁机制所需的上下文切换和线程阻塞的开销。

需要注意的是,尽管CAS操作可以避免锁带来的开销,但它并不能解决所有并发问题。在高并发场景下,由于CAS的自旋操作可能导致大量的CPU资源消耗,因此需要根据具体情况权衡使用。此外,CAS操作在并发度较高、竞争激烈的情况下可能会引发ABA问题,需要额外的措施进行处理(如使用版本号或标记位)来确保数据的一致性。
#9 synchronzied
锁升级过程
3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值