Synchronized的锁关系

锁的状态存放于对象头中,有4中锁状态, 无锁, 偏向锁, 轻量级锁, 重量级锁.

锁的状态也是依次升级且一旦升级就不会降级,

偏向锁:

从jdk.6加入对锁的优化手段, 偏向锁的场景为锁不存在竞争, 一个时刻总是仅有一个线程持有. 如果锁不存在竞争那么CAS的操作也是多余的.

偏向锁的核心思想是当一个线程获取了锁, 那么这个锁就进入了偏向状态, 这里的"偏"就是偏心于一个线程的意思.

偏向锁的获得和撤销流程

 

获取锁步骤:

1)判断锁对象是否是偏向锁(即锁标志位为01,偏向锁位为1),若为偏向锁状态执行2)。

2)判断锁对象的线程ID是否为当前线程的ID,如果是则说明已经获取到锁,执行代码块;否则执行3)。

3)当前线程使用CAS更新锁对象的线程ID为当前线程ID。如果成功,获取到锁;否则执行4)

4)当到达全局安全点,当前线程根据Mark Word中的线程ID通知持有锁的线程挂起,将锁对象Mark Word中的锁对象指针指向当前堆栈中最近的一个锁记录,偏向锁升级为轻量级锁,恢复被挂起的线程。

释放锁步骤:

偏向锁采用一种等到竞争出现时才释放锁的机制。当其他线程尝试竞争偏向锁时,当前线程才会释放释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点。

准确来说, 应该是撤销偏向锁或者重偏向

1)首先暂停持有偏向锁的线程。

2)撤销偏向锁,恢复到无锁状态或轻量级锁状态。

偏向锁的撤销流程图:

 

偏向锁: 状态

匿名偏向: 即初始状态,Mark Word中Thrad ID为null, 将当前线程id用CAS操作写入Mark Word中

重偏向: Mark Word里的epoch与 该对象类型klassOop中的epoch不一致时, 可以被重偏向, 将当前线程id用CAS操作写入Mark Word中

已偏向: Mark Word中Thrad ID有值, 当前线程ID比较, 一致时知己获取, 不一致时会膨胀为轻量级锁

偏向锁: 重偏向(Bulk Rebias)机制

场景为多个线程之前不存在竞争关系, 交替使用该锁, 因为偏向锁在执行完同步代码块时是没有释放锁的, 重偏向就是针对于多线程交替使用该锁的优化.

前置知识: 当一个对象实例化时, 会根据klassOop为原型进行实例化对象, 其中也包括了Mark Word部分. epoch就在Mark Word中

引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性

除了对象中的 epoch, 对象所属的类 klass 信息中,也会保存一个 epoch 值

每当遇到一个全局安全点时, 挂起所有线程,给KlassOop.epoch + 1

扫描所有持有KlassOop的实例对象的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将正在被锁定的对象中的epoch+1。

退出安全点后, 恢复暂停线程, 当有线程需要尝试获取偏向锁时, 直接检查KlassOop.epoch值是否与目标实例对象中存储的 epoch 值相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

 

如:

t1获取锁,KlassOop.epoch=1,lock实例.epoch=1,执行完同步代码,

t2获取锁, 给KlassOop.epoch + 1=2, 扫描到t1栈发现没有锁定, 实例不做+1操作

检查KlassOop.epoch(2)==lock实例.epoch(1), 不相等则可以重偏量

如果相等, 说明t1的同步代码还没执行完, 会暂停t1线程, 将t1的锁升级为轻量级锁, 再恢复t1线程.

https://www.jianshu.com/p/4758852cbff4

偏向锁: 升级为轻量级锁

存在超过一个线程竞争某一个对象时, 会发生偏向锁的撤销操作。 有趣的是, 偏向锁撤销后, 对象可能处于两种状态。

一种是不可偏向的无锁状态,(之所以不允许偏向, 是因为已经检测到了多于一个线程的竞争, 升级到了轻量级锁的机制)

hash code | age| 0 | 01

另一种是不可偏向的已锁 ( 轻量级锁) 状态

pointer to lock record | 00

之所以会出现上述两种状态, 是因为偏向锁不存在解锁的操作, 只有撤销操作。 触发撤销操作时:

  • 原来已经获取了偏向锁的线程可能已经执行完了同步代码块, 使得对象处于 “闲置状态”,相当于原有的偏向锁已经过期无效了。此时该对象就应该被直接转换为不可偏向的无锁状态。

  • 原来已经获取了偏向锁的线程也可能尚未执行完同步代码块, 偏向锁依旧有效, 此时对象就应该被转换为被轻量级加锁的状态

 

轻量级锁:

jdk1.6加入的锁优化手段, 为了优化重量级锁从用户状态与系统内核状态转换互斥的性能消耗.

轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

 

获取锁步骤:

1)判断是否处于无锁状态,若是,则JVM在当前线程的栈帧中创建锁记录(Lock Record)空间,用于存放锁对象中的Mark Word的拷贝,官方称为Displaced Mark Word;否则执行步骤3)。

2)当前线程尝试利用CAS将锁对象的Mark Word更新为指向锁记录的指针。如果更新成功意味着获取到锁,将锁标志位置为00,执行同步代码块;如果更新失败,执行步骤3)。

3)判断锁对象的Mark Word是否指向当前线程的栈帧,若是说明当前线程已经获取了锁,执行同步代码,否则说明其他线程已经获取了该锁对象,执行步骤4)。

4)当前线程尝试使用自旋来获取锁,自旋期间会不断的执行步骤1),直到获取到锁或自旋结束。因为自旋锁会消耗CPU,所以不能无限的自旋。如果自旋期间获取到锁(其他线程释放锁),执行同步块;否则锁膨胀为重量级锁,当前线程阻塞,等待持有锁的线程释放锁时的唤醒。

释放锁步骤:

1)从当前线程的栈帧中取出Displaced Mark Word存储的锁记录的内容。

2)当前线程尝试使用CAS将锁记录内容更新到锁对象中的Mark Word中。如果更新成功,则释放锁成功,将锁标志位置为01无锁状态;否则,执行3)。

3)CAS更新失败,说明有其他线程尝试获取锁。需要释放锁并同时唤醒等待的线程。

 

重量级锁:

重量级锁在Synchronized的原理章节单独说明

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的问题。synchronized升级过程是Java的优化技术之一,它可以减少的竞争,提高程序的执行效率。在Java中,每个对象都有一个(也称为内置或监视器),线程在执行同步代码块时会获取该对象的,并在代码块执行完成后释放该。当多个线程同时访问同一个对象的同步代码块时,就会出现竞争的情况,影响程序的执行效率。 在Java6之前,synchronized的实现存在较大的性能问题,当多个线程竞争同一个时,会导致线程频繁地进入和退出阻塞状态,增加了线程切换的开销,降低了程序的执行效率。为了解决这个问题,Java6引入了偏向、轻量级和重量级等优化技术,并采用升级的方式在不同之间进行转换,从而提高程序的执行效率。 具体来说,当一个线程第一次进入同步代码块时,会先尝试获取对象的偏向,如果对象没有被其他线程访问过,那么当前线程会获取到该对象的偏向,并将当前线程ID记录在对象头中,以后该线程每次进入同步代码块时都无需重新获取,直接使用偏向即可。如果有其他线程访问了该对象,那么当前线程会尝试将偏向升级为轻量级。轻量级是一种自旋,它采用CAS(Compare And Swap)指令来保证线程的原子性,避免了线程阻塞和唤醒的开销。如果当前线程获取轻量级成功,那么就可以在自旋过程中执行同步代码,避免了线程阻塞和唤醒的开销。如果轻量级获取失败,那么当前线程就会尝试将轻量级升级为重量级,此时当前线程会阻塞等待其他线程释放该对象的,直到获取到重量级才能执行同步代码。 总之,synchronized的升级过程是Java的优化技术之一,它可以提高程序的执行效率,减少的竞争。 ### 回答2: synchronizedJava中用于保证多个线程访问同一个共享资源时的线程安全的关键字。当一个线程想要执行synchronized修饰的同步代码块时,需要先获取对象的,如果被其他线程占用,则当前线程会被阻塞,直到获取到为止。 synchronized升级过程分为偏向、轻量级和重量级三个阶段。 首先,在没有竞争的情况下,将被优化为偏向。偏向的作用是,当一个线程获取到时,会在对象的头部标记记录该线程的ID。在下次该线程再次申请时,无需再次进行同步操作,直接获取即可。 然后,如果有多个线程竞争同一个,偏向将转为轻量级。这时每个线程都会在自己的线程栈帧中保存的副本。线程会通过CAS(比较并交换)来进行的获取和释放,而不再阻塞线程。 最后,如果多个线程仍然竞争同一个,轻量级将升级为重量级。重量级的实现是利用操作系统提供的互斥量机制,当一个线程获取后,其他线程将被阻塞,直到持有的线程释放的升级过程在多线程环境下进行,根据的状态切换来提高并发效率。通过合理地选择的类型以及的级别,可以更好地平衡性能与安全性之间的关系。 ### 回答3: synchronized升级过程是指在Java中保证多线程访问同步代码时的一种优化机制。其主要目的是提高多线程并发访问共享资源时的性能和效率。 当一个线程尝试进入同步代码块时,会先尝试获取对象的无状态。如果成功获取无状态,则可以直接执行同步代码,并将对象标记为偏向。这是的第一级别,也是最轻量级的。如果在此时另一个线程也想要进入同步代码,就会造成竞争。 如果存在竞争,偏向就会升级为轻量级。轻量级是通过在对象头中的标识字段中记录指向线程栈中记录的指针来实现的。如果线程竞争太激烈,轻量级就会升级为重量级。 重量级是指同步代码块被多个线程访问时,会将线程阻塞并等待释放。重量级采用操作系统的互斥量实现,所以比较耗时和耗资源。 在升级过程中,的状态会从无状态到偏向,再到轻量级,最后到重量级。在逐步升级的过程中,的开销也会逐渐增加。 需要注意的是,在JDK 6之后,引入了消除和膨胀机制。消除指的是JVM在编译器优化时发现某些代码分支中不存在线程竞争时,会去除相应的操作;膨胀指的是JVM会根据竞争情况,将轻量级升级为重量级。 综上所述,synchronized升级过程是为了提高多线程并发访问同步代码时的性能和效率。通过从无状态到偏向,再到轻量级,最后到重量级的升级过程,JVM可以根据竞争情况选择最适合的状态,以实现最佳的性能和资源利用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值