JUC多并发编程-->Synchronized与锁升级、AQS、ReentrantReadWriteLock

无锁、偏向锁、轻量级锁、重量级锁
对象头标志位
在这里插入图片描述

  • 偏向锁:MarkWord存储的是偏向的线程ID;
  • 轻量锁:MarkWord存储的是指向线程栈中LockRecord的指针重量锁
  • MarkWord存储的是指向堆中的monitor对象的指针;

无锁

obejct.hashCode()调用之后才显示hashcode编码

偏向锁

当一段同步代码一直被同一个线程多次访问,由于只有一个线程,那么该线程在后续访问时便会自动获得锁。
多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况,
偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。
偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也即偏向锁在资源没有竞争情况下消除了同步语句,懒的连CAS操作都不做了,直接提高程序性能
过程:

  • 只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接会去检査锁的MarkWord里面是不是放的自己的线程ID)。
  • 如果相等,表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。、
  • 如果不等,表示发生了竞争,锁已经不是总是偏向于同一个线程了,这个时候会尝试使用CAS来替换MarkWord里面的线程ID为新线
    程的ID,竞争成功,表示之前的线程不存在了,MarkWord里面的线程ID为新线程的ID,锁不会升级,仍然为偏向锁:竞争失败,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。注意,偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
    技术实现:
    一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mank Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了。

撤销偏向锁:

  • 当有另外线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁
  • 竞争线程尝试CAS更新对象头失败,会等待到全局安全点此时不会执行任何代码)撤销偏向锁。

俩种情况

  • 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级。此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
  • 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向 。
  • 在这里插入图片描述
    java15偏向锁逐渐废除

轻量级锁

多线程竞争,但是任意时刻最多只有一个线程竞争,即不存在锁竞争太过激烈的情况,也就没有线程阻塞。

  • JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间displaced Mark Word。若一个线程获得锁时发现是轻量级锁。会把锁的MarkWord复制到自己的displaced Mark Word里面。然后线程尝试用CAS将锁的Mark Word替换为指向锁记录的指针,如果成功,当前线程获得锁,如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明再与其他线程竞争锁,当前线程就尝试用自旋来获取锁。
    自旋CAS:不断尝试获取锁,能不升级就不升级,尽量不要堵塞。
  • 在释放锁时,当前线程会使用CAS操作将Displaced Mark Word的内容复制回锁的Mark Word里面。如果没有发生竞争,那么这个复
    制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁开级成了重是级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻寒
    的线程。
    在这里插入图片描述
    自适应自旋锁的原理:
  • 线程自旋成功,下次自旋最大次数会增加,因为JVM认为上次成功,则这次成功概率也很大
  • 线程很少自旋成功,下次会减少自旋次数,甚至不自旋,避免CPU空转。

争夺轻量级锁失败时,自旋尝试抢占锁
轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁

重量级锁

hashCode去哪了?

在这里插入图片描述

锁的优缺点

在这里插入图片描述

JIT 即时编译器

锁消除问题,锁无效
在这里插入图片描述
锁粗化
假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能

AQS

AQS(AbstractQueuedSynchronizer)抽象的队列同步器
是用来实现锁或者其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给谁的问题。
整体就是一个抽象的FIFO队列来完成资源获取线程的派对公主,并通过一个int类变量表示持有锁的状态。
在这里插入图片描述
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁的分配。这个机制主要是通过CLH队列 的变体实现的。将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。他将要请求共享资源的线程以及自身的等待状态封装成队列的节点对象(node),通过CAS、自旋、LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。
AQS框架
在这里插入图片描述
AQS使用一个volatie的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改
在这里插入图片描述
int类型的同步状态state,0代表空闲,1代表非空闲。
AQS的CLH队列
在这里插入图片描述
Node的等待状态waitState成员变量,volatile int waitStatus
在这里插入图片描述
公平锁和非公平锁的源码区别
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
入队操作

ReentrantLock\ReentrantReadWriteLock\SampedLock

可重入锁,可重入读写锁,邮戳锁

可重入读写锁

一个资源可以被多个读线程访问,或者一个写线程访问,但不能同时存在读写线程。
读写互斥,读读共享,读没有完成时,其它线程写锁无法获得
适用于读多写少。
锁饥饿:当读线程较多时,写线程抢不到锁,导致锁饥饿
锁降级:写锁的降级,降级为了读锁:

  • 如果一个线程有写锁,在没有释放写锁的情况下,还可以继续获得读锁。这就是写锁的降级,降级为了读锁。
  • 按照惯例:先获取写锁,再获取读锁,再释放写锁
  • 释放了写锁就变为读锁
  • 读锁变写锁是不可能的,读没有完成时,写锁无法获得。
    演变:
    在这里插入图片描述
    锁降级源码
    在这里插入图片描述
    在这里插入图片描述

stampedLock(乐观锁)

比读写锁更快的锁
stamp long类型。代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
由锁饥饿引出:

  • “公平”策略一定程度上可以缓解这个问题,但是牺牲了系统的吞吐量。
  • stampedLock: 对短的只读代码,使用乐观的方式,通常可以减少争用并提高吞吐量

特点:
在这里插入图片描述

三种访问模式:
在这里插入图片描述
读的过程允许写的介入
缺点:
在这里插入图片描述

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值