Java JMM/volatile/CAS/UnSafe/原子类
一、JMM
1. JMM是Java Memory Model(Java内存模型)的简称,它是一组规范,目的是屏蔽系统和硬件的差异,是一个抽象的概念,并不真实存在。
A. 线程解锁前,必须要把共享的变量值刷新回主内存;
B. 线程加锁前,必须读取主内存的最新值到自己的工作内存;
C. 必须是一把锁。
2. JMM模型特征(并发三要素)
A. 原子性:一个或多个操作要么全部执行并且执行的过程中不会被任何因素打断,要么就都不执行,这是分时复用引起的;【java.concurrent.Atomic.* 都是原子类】
B. 可见性:每个工作线程都有自己的工作内存,所以当某个线程修改完某个共享变量之后,在其他的线程中,能立刻观察到该变量已经被修改,这是CPU缓存引起的;
CPU缓存:分L1、L2、L3三个级别,级别越小越接近CPU;
C. 有序性:程序执行的顺序应按照代码的先后顺序执行,这是指令重排序引起的。
3. Happens-Before规则
A. 单一线程原则:在一个线程内,在程序前面的操作先行发生于后面的操作;
B. 管道锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作;
C. volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作;
D. 线程启动规则:Thread对象的start()方法调用先行发生于此线程的每一个动作;
E. 线程加入规则:Thread对象的结束先行发生于join()方法返回;
F. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过interrupted()方法检测到是否有中断发生;
G. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始;
H. 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。
4. 内存交互操作
A. lock(锁定):作用于主内存的变量,把一个变量表示线程独占状态;
B. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
C. read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
D. load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
E. use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
F. assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
G. store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
H. write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
二.、volatile
1. volatile是Java提供的一种轻量级的同步机制,它不会引起线程上下文切换和调度
2. 特点
A. 保证可见性;
B. 不保证原子性,原子性的保证可以使用Atomic包下原子类;
C. 禁止指令重排序,指令重排序是编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段,在单线程下,不管如何排序,结果不会变。
3. 可见性实现原理
JVM底层volatile是采用内存屏障指令来实现的
A. 它确保指令重排序时不会把后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障之后的位置;
B. 它会强制对缓存的修改操作立即写入内存;
C. 如果是写操作,它会导致其他CPU中对应的缓存行失效。
三、CAS
1. 定义:CAS(Compare-And-Swap)即比较并交换,CAS是一种无锁算法,操作是原子性的,CAS有三个操作数,内存值V、旧的预期值A、要修改的新值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;
2. 原理:利用了现代处理器都支持的CAS的指令,循环这个指令,知道成功为止;
3. 问题
A. ABA问题:如果一个值原来为A,变成了B,然后又变成了A,那么使用CAS进行检查时则会发现它的值没有变化,但是实际上变化了。解决办法是使用版本号、AtomicStampedReference;
package com.ruhuanxingyun.javabasic.cas; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicInteger; /** * @description: CAS ABA问题 * @author: ruphie * @date: Create in 2021/3/10 20:59 * @company: ruhuanxingyun */ @Slf4j public class CasABA { private static AtomicInteger atomicInteger = new AtomicInteger(10); public static void main(String[] args) { new Thread(() -> { int expect = atomicInteger.get(); log.info("操作线程{}的期望值为{}", Thread.currentThread().getName(), expect); try { Thread.sleep(5000); int update = expect + 1; boolean status = atomicInteger.compareAndSet(expect, update); log.info("操作线程{}的CAS操作状态为{}", Thread.currentThread().getName(), status); } catch (InterruptedException e) { e.printStackTrace(); } }, "main").start(); new Thread(() -> { int expect = atomicInteger.get(); log.info("测试线程{}的期望值为{}", Thread.currentThread().getName(), expect); try { Thread.sleep(1000); int update = atomicInteger.incrementAndGet(); log.info("测试线程{}的更新值为{}", Thread.currentThread().getName(), update); update = atomicInteger.decrementAndGet(); log.info("测试线程{}的更新值为{}", Thread.currentThread().getName(), update); } catch (InterruptedException e) { e.printStackTrace(); } }, "test").start(); } }
package com.ruhuanxingyun.javabasic.cas; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicStampedReference; /** * @description: CAS AtomicStampedReference解决ABA问题 * @author: ruphie * @date: Create in 2021/3/10 20:59 * @company: ruhuanxingyun */ @Slf4j public class CasABAReference { private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10, 1); public static void main(String[] args) { new Thread(() -> { int expectReference = atomicStampedReference.getReference(); log.info("操作线程{}的期望值为{}", Thread.currentThread().getName(), expectReference); try { int updateReference = expectReference + 1; int expectStamp = atomicStampedReference.getStamp(); int updateStamp = expectStamp + 1; Thread.sleep(5000); boolean status = atomicStampedReference.compareAndSet(expectReference, updateReference, expectStamp, updateStamp); log.info("操作线程{}的CAS操作状态为{}", Thread.currentThread().getName(), status); } catch (InterruptedException e) { e.printStackTrace(); } }, "main").start(); new Thread(() -> { int expectReference = atomicStampedReference.getReference(); log.info("测试线程{}的期望值为{}", Thread.currentThread().getName(), expectReference); try { int expectStamp = atomicStampedReference.getStamp(); Thread.sleep(1000); atomicStampedReference.compareAndSet(expectReference, expectReference + 1, expectStamp, expectStamp + 1); log.info("测试线程{}的更新值为{}", Thread.currentThread().getName(), atomicStampedReference.getReference()); expectReference = atomicStampedReference.getReference(); expectStamp = atomicStampedReference.getStamp(); atomicStampedReference.compareAndSet(expectReference, expectReference - 1, expectStamp, expectStamp + 1); log.info("测试线程{}的更新值为{}", Thread.currentThread().getName(), atomicStampedReference.getReference()); } catch (InterruptedException e) { e.printStackTrace(); } }, "test").start(); } }
B. 循环时间长开销大;
C. 只能保证一个共享变量的原子操作。
四、UnSafe
1. 作用:是位于sun.misc包下的一个类,主要提供一些用于执行级别、不安全操作的方法。
2. 功能:包含内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。
五、原子类
1. 原子更新基本类型
A. AtomicBoolean:原子更新布尔类型;
B. AtomicInteger:原子更新整型;
C. AtomicLong:原子更新长整型。
2. 原子更新数组
A. AtomicIntegerArray:原子更新整型数组里的元素;
B. AtomicLongArray:原子更新长整型数组里的元素;
C. AtomicReferenceArray:原子更新引用类型数组里的元素;
3. 原子更新引用类型
A. AtomicReference:原子更新引用类型;
B. AtomicStampedReference:原子更新引用类型, 内部使用Pair来存储元素值及其版本号;
C. AtomicMarkableReferce:原子更新带有标记位的引用类型;
4. 原子更新字段类
A. AtomicIntegerFieldUpdater:原子更新整型的字段的更新器;
B. AtomicLongFieldUpdater:原子更新长整型字段的更新器;
C. AtomicStampedFieldUpdater:原子更新带有版本号的引用类型;
D. AtomicReferenceFieldUpdater:原子更新引用字段。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗