Synchronized 同步锁详解

大家好 我是积极向上的湘锅锅💪💪💪

1. 简介

Synchronized是JAVA的关键字,是基于JVM层面的独占式的悲观锁,同时也属于可重入锁
经常来用于解决多个线程之间访问访问资源的同步性,保证在同一时刻只有一个线程访问这个资源

2. 如何使用

  • 修饰实例方法
    当修饰实例方法的时候,锁的是该实例对象,进入同步代码前要获得当前对象实例的锁,使用的指令是monitor,线程试图获取锁也就是获取 对象监视器 monitor 的持有权

    每个对象都有一个monitor对象,加锁就是在竞争monitor 对象,方法加锁是通过一个标记位来判断的
    
  • 修饰静态方法
    也就是给当前类加锁,会作用于类的所有实例,不管再怎么new对象,这个静态方法始终只有一份,所以设想一个场景,一个线程A调用该类的非静态synchronized方法,一个线程B要调用该类的静态synchronized,这个是可以成功的,不会发生互斥现象

  • 修饰代码块
    这个加锁锁的就是咱们的实例对象,如果要进入代码块,就必须要先获得该对象的锁,同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置

     monitorenter会尝试获取锁,如果锁计数器为0,则获取锁成功,获取后奖锁计数器为置为1
     monitorexit会释放锁,将锁计数器重新置为0,让其他线程来获取锁
    

总结:
如果修饰是实例方法,代码块,JVM 会尝试获取实例对象的锁
如果是静态方法,JVM 会尝试获取当前 class 的锁

3. 核心组件

  • Wait Set:那些调用wait方法的被阻塞的线程被放置在这里
  • Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中
  • Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中
  • OnDeck:任意时刻,只有一个线程正在竞争锁资源,该线程称为OnDeck
  • Owener:当前已经获取到锁的线程称为Owener
  • !Owener:当前释放锁的线程

4. Synchronized 实现

  1. JVM每次从队列的尾部取出一个数据用于竞争候选者(OnDeck),但是在并发情况下,Contention List会被大量的线程进行CAS访问,所有会将一部分线程放到Entry List里面去

  2. 当Owener进行unlock解锁的时候,会将将 ContentionList 中的部分线程迁移到 EntryList 中,并指定EntryList 中的某个线程为 OnDeck 线程(一般是最先进去的那个线程)

  3. Owener线程并不直接把锁给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck 需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM 中,也把这种选择行为称之为“竞争切换”

  4. OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList中。如果 Owner 线程被 wait 方法阻塞,则转移到 WaitSet 队列中,直到某个时刻通过 notify或者 notifyAll 唤醒,会重新进去 EntryList 中

处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,当然不同情况的阻塞也不同

Synchronized 是非公平锁。 Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源

5. Synchronized 的劣势

我们知道Synchronized的monitor是基于操作系统的Mutex Lock实现的,而java是无法直接跟操作系统交互的,所以需要从用户态到内核态的转变,而这个转变是很花时间的,但是从Java1.6开始,synchronized 进行了很多的优化,有自旋锁、适应性自旋锁、偏向锁、轻量级锁、锁消除、锁粗化等技术来减少锁操作的开销。

6. Synchronized的锁优化

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁

  • 自旋锁(轻量级锁)
    原理是什么呢,其实就是在竞争锁资源的时候,发现锁已经被其他线程占有了,而如果持有锁的线程能在很短时间内释放锁资源,那么那些等待获取锁的线程就不必再做内核态到用户态的切换进入阻塞挂起状态,就只需要等一等(自旋),等锁释放之后去获取锁

    优缺点:
    对于那些锁竞争不激烈的情况,且占用锁时间非常短,那么自旋锁就可以很大程度的提升性能,只要自旋的消耗小于线程阻塞再挂起的消耗,那就是有意义的
    当然线程自旋是要消耗cpu的,我们需要设置一个自旋等待的最大时间,不然会一直占用cpu资源,当然,如果同一时间有很大的线程同时来获取锁,会导致锁的获取时间过长,这也是会导致线程自旋的消耗大于线程阻塞再挂起的消耗,所以这俩种情况同时成立,我们就要关闭自旋锁

  • 适应性自旋锁
    顾名思义,自旋锁的目的是为了占着 CPU 的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?是由前一次再同一个锁上的自旋时间以及锁的拥有者的状态决定,基本认为一个线程上下文切换的时间是最佳的一个时间,当然JVM还做了一些优化,感兴趣的可以了解一下

  • 偏向锁
    在某些不存在多线程竞争锁的情况,就可以使用偏向锁,目的是消除这个线程重入(CAS)的开销,看起来得到了偏护,引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,只偏向锁需要在第一次置换ThreadID 的时候依赖一次 CAS 原子指令,而一旦出现多线程竞争的情况就必须撤销偏向锁

  • 轻量级锁
    “轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁

偏向锁与轻量级锁:
轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能


7. synchronized 锁升级原理:

  • 在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id
  • 再次进入的时候会先判断threadid 是否与其线程 id 一致如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁
  • 执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中,synchronized锁的升级是指在不同的场景下,锁的实现方式会有所不同,以提高性能和并发控制的效率。下面是synchronized锁的升级详解: 1. 偏向锁(Biased Locking):当一个线程访问同步块时,首先会尝试获取偏向锁。如果偏向锁未被其他线程占用,则当前线程会获得偏向锁,并标记为偏向线程ID。这样,在后续进入同步块时,无需再进行锁的竞争,提高了性能。只有当其他线程尝试获取偏向锁时,才会撤销偏向锁状态。 2. 轻量级锁(Lightweight Locking):当多个线程尝试竞争同一个锁时,偏向锁会升级为轻量级锁。轻量级锁使用CAS操作来实现加锁和解锁,避免了线程阻塞和唤醒的开销。如果CAS操作失败,表示存在竞争,锁会升级为重量级锁。 3. 重量级锁(Heavyweight Locking):当轻量级锁竞争失败时,锁会升级为重量级锁。重量级锁使用操作系统的互斥量(Mutex)来实现,被阻塞的线程会进入等待状态,直到锁被释放。重量级锁的竞争会导致线程的上下文切换和调度开销增加,性能较低。 synchronized锁的升级过程是根据实际情况和竞争情况动态进行的。在大多数情况下,锁的升级是逐级升级的,即从偏向锁到轻量级锁,再到重量级锁。这种锁的升级机制是为了在减少锁竞争时提供更好的性能,并在存在竞争时保证线程安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

owensweat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值