• Synchronized锁升级


    线程状态

    要想把锁说清楚,一个重要的概念不得不提,那就是线程和线程的状态。锁和线程的关系是怎样的呢,举个简单的例子你就明白了。

    这个例子中,每个顾客是一个线程。 柜台前面的那把椅子,就是。 柜台后面的柜员,就是共享资源。 你发现无法直接办理业务,要取号等待的过程叫做阻塞。 当你听到叫你的号码的时候,你起身去办业务,这就是唤醒。 当你坐在椅子上开始办理业务的时候,你就获得锁。 当你办完业务离开的时候,你就释放锁

    对于线程来说,一共有五种状态,分别为:初始状态(New) 、就绪状态(Runnable) 、运行状态(Running) 、阻塞状态(Blocked) 和死亡状态(Dead) 。

    Synchronized的性能变化

    • Java5以前,只有Synchronized,这个是操作系统级别的重量级操作,重量级锁,假如锁的竞争比较激烈的话,性能下降严重。

     

    • Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁,需要有个逐步升级的过程,别一开始就捅到重量级锁

    synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略

    锁的类型

    在Java SE 1.6里Synchronied同步锁,一共有四种状态:无锁偏向锁轻量级锁重量级锁,它会随着竞争情况逐渐升级。锁可以升级但是不可以降级,目的是为了提供获取锁和释放锁的效率。

    锁膨胀方向: 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 (此过程是不可逆的)

    无锁

    代码展示

    在这里插入图片描述偏向锁

    引入背景:在大多实际环境下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获取,那么在同一个线程反复获取所释放锁中,其中并还没有锁的竞争,那么这样看上去,多次的获取锁和释放锁带来了很多不必要的性能开销和上下文切换

    偏向锁的撤销

    轻量级锁

    ​在JDK 1.6之后引入的轻量级锁,需要注意的是轻量级锁并不是替代重量级锁的,而是对在大多数情况下同步块并不会有竞争出现提出的一种优化。它可以减少重量级锁对线程的阻塞带来的线程开销。从而提高并发性能。

    自旋锁与自适应自旋锁

    引入背景:大家都知道,在没有加入锁优化时,Synchronized是一个非常“胖大”的家伙。在多线程竞争锁时,当一个线程获取锁时,它会阻塞所有正在竞争的线程,这样对性能带来了极大的影响。在挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作对系统的并发性能带来了很大的压力。同时HotSpot团队注意到在很多情况下,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和回复阻塞线程并不值得。在如今多处理器环境下,完全可以让另一个没有获取到锁的线程在门外等待一会(自旋),但不放弃CPU的执行时间。等待持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需要让线程执行一个忙循环(自旋),这便是自旋锁由来的原因。

    就像去银行办业务的例子,当你来到银行,发现柜台前面都有人的时候,你需要取一个号,然后再去等待区等待,一直等待被叫号。这个过程是比较浪费时间的,那么有没有什么办法改进呢?

    有一种比较好的设计,那就是银行提供自动取款机,当你去银行取款的时候,你不需要取号,不需要去休息区等待叫号,你只需要找到一台取款机,排在其他人后面等待取款就行了。

    之所以能这样做,是因为取款的这个过程相比较之下是比较节省时间的。如果所有人去银行都只取款,或者办理业务的时间都很短的话,那也就可以不需要取号,不需要去单独的休息区,不需要听叫号,也不需要再跑到对应的柜台了。

    而在程序中,Java虚拟机的开发工程师们在分析过大量数据后发现:共享数据的锁定状态一般只会持续很短的一段时间,为了这段时间去挂起和恢复线程其实并不值得。

    如果物理机上有多个处理器,可以让多个线程同时执行的话。我们就可以让后面来的线程“稍微等一下”,但是并不放弃处理器的执行时间,看看持有锁的线程会不会很快释放锁。这个“稍微等一下”的过程就是自旋。

    • 对于去银行取钱的你来说,站在取款机面前等待和去休息区等待叫号有一个很大的区别:

      • 那就是如果你在休息区等待,这段时间你什么都不需要管,随意做自己的事情,等着被唤醒就行了。

      • 如果你在取款机面前等待,那么你需要时刻关注自己前面还有没有人,因为没人会唤醒你。

      • 很明显,这种直接去取款机前面排队取款的效率是比较高。

    所以呢,自旋锁和阻塞锁最大的区别就是,到底要不要放弃处理器的执行时间。对于阻塞锁和自旋锁来说,都是要等待获得共享资源。但是阻塞锁是放弃了CPU时间,进入了等待区,等待被唤醒。而自旋锁是一直“自旋”在那里,时刻的检查共享资源是否可以被访问。

    由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

    重量级锁

    锁的优缺点对比

    锁的指向

    总结锁升级过程

    锁消除

    • 锁消除:从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用
    1. **
    2. * 锁消除
    3. * 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,
    4. * 极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用
    5. */
    6. public class LockClearUPDemo{
    7. static Object objectLock = new Object();//正常的
    8. public void m1(){
    9. //锁消除,JIT会无视它,synchronized(对象锁)不存在了。不正常的
    10. Object o = new Object();
    11. synchronized (o){
    12. System.out.println("-----hello LockClearUPDemo"+"\t"+o.hashCode()+"\t"+objectLock.hashCode());
    13. }
    14. }
    15. public static void main(String[] args){
    16. LockClearUPDemo demo = new LockClearUPDemo();
    17. for (int i = 1; i <=10; i++) {
    18. new Thread(() -> {
    19. demo.m1();
    20. },String.valueOf(i)).start();
    21. }
    22. }
    23. }

    锁粗化

    • 锁粗化:假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能
    1. /**
    2. * 锁粗化
    3. * 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,
    4. * 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能
    5. */
    6. public class LockBigDemo
    7. {
    8. static Object objectLock = new Object();
    9. public static void main(String[] args)
    10. {
    11. new Thread(() -> {
    12. synchronized (objectLock) {
    13. System.out.println("11111");
    14. }
    15. synchronized (objectLock) {
    16. System.out.println("22222");
    17. }
    18. synchronized (objectLock) {
    19. System.out.println("33333");
    20. }
    21. },"a").start();
    22. new Thread(() -> {
    23. synchronized (objectLock) {
    24. System.out.println("44444");
    25. }
    26. synchronized (objectLock) {
    27. System.out.println("55555");
    28. }
    29. synchronized (objectLock) {
    30. System.out.println("66666");
    31. }
    32. },"b").start();
    33. }
    34. }
  • 相关阅读:
    STL的set和map的基本使用
    【大学英语视听说上】课后主题作文
    CSP 202005-1 重复局面
    计算机毕业设计ssm校园快递代取管理系统t914g系统+程序+源码+lw+远程部署
    关于离子色谱仪的结构和应用原理分析
    EN 1504-5混凝土结构保护和修理用产品混凝土喷射—CE认证
    TRC hCCR2 拮抗剂丨艾美捷 TRC Dimesylate解决方案
    【华为OD机试真题 python】矩阵扩散 【2022 Q4 | 200分】
    30V-STM32设计项目
    求质数(埃氏筛法)代码
  • 原文地址:https://blog.csdn.net/weixin_63566550/article/details/126167573