锁的优化过程
Posted 哔卟哔卟_: )
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了锁的优化过程相关的知识,希望对你有一定的参考价值。
synchronized的优化过程
JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。会根据情况, 依次进行升级
🔎无锁
线程之间不会引发安全问题,就不用加锁
🔎偏向锁
偏向锁不是真的 “加锁”, 只是先让线程针对锁做个标记(这个过程很快)
如果代码执行过程中没有其他线程来竞争这个锁, 那么就不用加锁
如果有其他线程竞争这个锁, 那么偏向锁就会真的加锁, 变成轻量级锁
偏向锁是synchronized内部做的工作
synchronized 会针对某个对象进行加锁, 偏向锁就是先去对加锁的对象做个标记
当有其他线程竞争这个锁时, JVM就会通知 偏向锁 升级成 轻量级锁
🔎轻量级锁
轻量级锁基于自旋锁实现
自旋锁
优点: 能够第一时间拿到锁
缺点: 一直消耗CPU,做不了其他事情(忙等)
如果锁竞争过于激烈(CPU消耗过大)
那么 synchronized 就会从 轻量级锁 升级成 重量级锁
🔎重量级锁
重量级锁基于挂起等待锁实现
挂起等待锁
优点: 不再消耗大量CPU资源
缺点: 锁被释放后不能第一时间拿到锁
当 synchronized 升级成重量级锁, 就意味着线程要暂时放弃 CPU, 由内核进行后续调度
🔎其他的锁优化
🌸锁消除
锁消除是编译器做的优化手段
非必要不加锁,
编译器发现代码不会引起线程安全问题却加锁的时候, 就会把锁给取消掉
举个栗子🥝
StringBuffer 是内部方法里面加了锁🔒
但是上述代码并不会引起线程安全问题
所以编译器就会进行优化(锁消除)
🌸锁粗化
锁粒度
synchronized 代码块中包含代码的多少
包含的代码越多, 锁粒度就越粗
包含的代码越少, 锁粒度就越细
一般写代码时, 希望锁的粒度细(代码块中包含的代码少)
这样串行的时间就会少一些, 并发的时间就会多一些
如果某个场景中涉及到频繁的加锁, 解锁操作
编译器就会将其优化, 优化成一个更粗粒度的锁
这就是锁粗化
该场景中由于涉及到频繁的加锁, 解锁操作
于是编译器就将其优化成了一个更粗粒度的锁
一个更粗粒度的锁
举个栗子🥝
滑稽老哥去给领导汇报工作
滑稽老哥给领导打电话汇报A工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥给领导打电话汇报B工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥给领导打电话汇报C工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥打电话汇报工作时, 此时如果有其他的电话打给领导, 只能阻塞等待
汇报完毕, 再次汇报时, 可能其他人也在给领导打电话, 滑稽老哥阻塞等待
滑稽老哥需要再次拨打电话汇报其他工作情况(重新竞争锁)
针对上述情况, 编译器将该操作优化成了一个更粗粒度的锁
滑稽老哥向领导依次汇报A, B, C工作情况, 而不是分别打电话进行汇报
(滑稽老哥打电话给领导, 汇报工作A, B, C的情况, 挂断电话)
🔎结尾
创作不易,如果对您有帮助,希望您能点个免费的赞👍
大家有什么不太理解的,可以私信或者评论区留言,一起加油
Java多线程锁的优化策略
锁的优化策略
编码过程中可采取的锁优化的思路有以下几种:
1:减少锁持有时间
例如:对一个方法加锁,不如对方法中需要同步的几行代码加锁;
2:减小锁粒度
例如:ConcurrentHashMap采取对segment加锁而不是整个map加锁,提高并发性;
3:锁分离
根据同步操作的性质,把锁划分为的读锁和写锁,读锁之间不互斥,提高了并发性。
4:锁粗化
这看起来与思路1有冲突,其实不然。思路1是针对一个线程中只有个别地方需要同步,所以把锁加在同步的语句上而不是更大的范围,减少线程持有锁的时间;
而锁粗化是指:在一个间隔性地需要执行同步语句的线程中,如果在不连续的同步块间频繁加锁解锁是很耗性能的,因此把加锁范围扩大,把这些不连续的同步语句进行一次性加锁解锁。虽然线程持有锁的时间增加了,但是总体来说是优化了的。
5:锁消除
锁消除是编译器做的事:根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程(即不会影响线程空间外的数据),那么可以认为这段代码是线程安全的,不必要加锁。
Java虚拟机中采取的锁优化策略:
1:偏向锁:锁对象偏向于当前获得它的线程,如果在接下来的没有被其他线程请求,则持有该锁的线程将不再需要进行同步操作(即:持有该锁的线程在接下来的执行中遇到同步块时不再需要lock和unlock了,直接执行即可)。当另一个线程申请该锁时,当前线程的偏向模式才会结束,让出该锁。
2:轻量级锁:syncrhoized的底层实现是通过监视器monitor来控制的,而monitorenter与monitorexit这两个原语是依赖操作系统互斥(mutex)来实现的。
互斥会导致线程挂起,并在较短的时间内又需要重新调度回原线程的,较为消耗资源。轻量级锁(Lightweight Locking)利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救,减少多线程进入互斥的几率。
如果偏向锁失败,那么系统会进行轻量级锁的操作,使用CAS操作来尝试加锁。如果轻量级锁失败,才调用系统级别的重量级锁(syncrhoized)来加锁。
3:自旋锁:当线程申请锁时,锁被占用,则让当前线程执行一个忙循环(自旋),看看持有锁的线程是否会很快释放锁。如果自旋后还没获得锁,才进入同步阻塞状态;
3.1:自适应自旋:自旋的线程自旋的时间为同一个锁上一次线程自旋并获得锁的耗时。如果对于这个锁,自旋很少有成功的,就不自旋了,避免浪费CPU资源。
为了尽量避免使用重量级锁(操作系统层面的互斥),JVM首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
以上是关于锁的优化过程的主要内容,如果未能解决你的问题,请参考以下文章
如何在Java-EE中使用hibernate实现ManyToMany关系?
如何使用 java-ee8、microProfile 4.0、Openliberty 21 和 Docker 设置特定于阶段的微配置文件配置