内存模型(JMM)

JMM(java memory model):定义了一套在多线程读写共享数据时(成员变量、数组)时,对数据的可见性、有序性、原子性的规则和保障

原子性

synchronized加锁

可见性

不同线程可能在各自的 CPU 缓存中持有变量的副本,导致读到的值不一致

解决:volatile 易变关键字

他可以用来修饰成员变量和静态变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取他的值,线程操作volatile变量都是直接操作主存

synchronized也可以保证可见性,但是属于重量级操作,性能相对较低

有序性

同一个线程,JVM会在不影响正确性的前提下,调整语句的执行顺序

volatile 保证"前面的先发生,后面的后发生"

volatile修饰变量会禁止指令重排

JDK5以上版本volatile才真正有效

happens-before

happens-before规定了哪些写操作对其他线程的读操作可见,他是可见性和有序性的一套规则总结

  • 线程解锁之前对变量的写,对于接下来对m加锁的其他线程对该变量的都可见
  • 线程对volatile变量的写,对接下来 其他线程对该变量的读可见
  • 线程start前对变量的写,对该线程开始后对该变量的读可见
  • 线程t1打断t2前对变量的写,对于其他线程得知t2被打断后对变量的读可见
  • 对变量默认值的写,对其他线程对该变量的读可见
  • 具有传递性

变量都是指成员变量,静态变量(共享变量)

CAS

原子类 compare and swap,它体现的一种乐观锁的思想

获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰,结合CAS和volatile可以实现无锁并发,适用于竞争不激烈,多核CPU的场景下

竞争激烈,重试频繁发生,效率受影响

juc(java.util.concurrent)中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、AtomicBoolean等,底层采用CAS技术+volatile来实现

synchronized优化

每个对象都有对象头(包含class指针和Mark Word)。Mark Word平时存储这个对象的哈希码、分代年龄,当加锁时,这些信息就根据情况被替换为标记位、线程锁记录指针、重量级锁指针,线程ID等内容

Mark Word 中包含锁标志位用于标识当前锁状态,在不同状态下其余位的含义不同;当对象处于偏向锁时存储线程ID,处于轻量级锁时存储指向线程栈中锁记录的指针,处于重量级锁时存储指向 Monitor 的指针,这些信息在同一时刻只会存在一种,而不是同时存在。

synchronized 锁在运行过程中整体是向更高开销方向演进的,轻量级锁在竞争加剧时会膨胀为重量级锁且不会降级,而偏向锁在特定情况下可能被撤销并转为轻量级锁或无锁状态。

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作,java6引入了偏向锁来进一步优化,只有第一次使用CAS将线程ID设置到对象的Mark Work头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS

  • 撤销偏向需要将持锁线程升级为轻量级锁,这个过程中所有线程需要运行到安全点暂停(STW)
  • 访问对象的hashCode也会撤销偏向锁
  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,只需重置对象的threadID
  • 撤销偏向和重偏向都是批量进行的,一类为单位
  • 如果撤销偏向到达某个阈值,整个类的所有对象都会变为不可偏向的
  • 可以使用-XX:-UseBiasedLocking禁止偏向锁

轻量级锁

一个对象虽然有多个线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化

每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

重量锁

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,说明成功概率大,多自旋几次

加锁流程

synchronized 加锁时,首先尝试偏向锁,将线程ID写入对象头;若存在竞争,则升级为轻量级锁,将对象头的 Mark Word 拷贝到线程栈中的 Lock Record,并通过 CAS 将对象头替换为指向该 Lock Record 的指针;若 CAS 失败,会判断是否为当前线程重入,若是则直接进入临界区,否则自旋或升级为重量级锁;重量级锁通过 Monitor 实现,竞争线程会进入阻塞队列;释放锁时,轻量级锁通过 CAS 恢复对象头,若失败说明已膨胀为重量级锁,由 Monitor 机制唤醒等待线程。

其他优化

  1. 减少上锁时间
    同步代码块中尽量短
  2. 减少锁的粒度
    将一个锁拆分为多个锁提高并发度(例如hash表添加元素我不需要锁整个表,只需要锁对应的桶)
  3. 锁粗化
    多次循环进入同步代码块不如同步代码块中多次循环
    另外JVM可能做如下优化,把多次append的加锁操作粗化为一次
  4. 锁消除
    JVM会进行代码的逃逸分析,例如某个加锁对象时方法内部局部变量,不会被其他线程所访问到,这时候就会被JIT忽略掉所有同步操作
  5. 读写分离
    CopyOnWriteArrayList
    ConyOnWriteSet
相关推荐
不吃肥肉的傲寒42 分钟前
Graphify安装与结合claude code使用指南
java·python·ai编程·图搜索
seven97_top1 小时前
Tomcat的架构设计和启动过程详解
java·tomcat
djjdjdjdjjdj1 小时前
golang如何编写SSL证书到期检测工具_golang SSL证书到期检测工具编写总结
jvm·数据库·python
2301_813599551 小时前
HTML5中Canvas局部刷新区域重绘的算法优化
jvm·数据库·python
m0_602857761 小时前
mysql如何防止用户通过子查询窃取权限_MySQL安全参数设置
jvm·数据库·python
我是无敌小恐龙1 小时前
Java SE 零基础入门 Day05 类与对象核心详解(封装+构造方法+内存+变量)
java·开发语言·人工智能·python·机器学习·计算机视觉·数据挖掘
qq_189807031 小时前
Less如何处理CSS长文本换行_封装Mixin解决不同场景需求
jvm·数据库·python
HHHHH1010HHHHH1 小时前
CSS如何处理带有状态切换的折叠菜单_利用BEM修饰符管理状态
jvm·数据库·python
2401_871696521 小时前
如何在响应式网页中水平居中表单(CSS 绝对定位居中方案)
jvm·数据库·python
va学弟1 小时前
Agent入门开发(2):个性化功能添加
java·服务器·ai