【JVM】synchronized锁升级的过程

目录

如何从无锁状态到偏向锁状态:

偏向锁升级为轻量级锁:

轻量级锁到自旋锁的状态:

自旋锁升级为重量级锁:

下面是自旋锁升级到重量级锁的过程:

重量级锁的特点如下:


synchronized锁分为三种状态——偏向锁、轻量级锁、重量级锁

如何从无锁状态到偏向锁状态:

        当一个线程访问被synchronized修饰的代码块的时候,并获取对象的锁时,会在对象头中的Mark Word中记录该线程的ID,这就叫做偏向锁

        如果对象的Mark Word中的记录的线程ID为0,表示当前没有线程获取锁,则将该线程的ID记录到Mark Word当中,并将对象头指向偏向锁。

偏向锁升级为轻量级锁:

  • 当其他线程尝试获取一个对象的锁的时候,JVM发现对象头指向的线程ID和当前线程ID不一致的时候,会尝试将偏向锁升级为轻量级锁。
  • JVM也会在Mark Word中分配空间存储锁记录(锁记录包含指针、标志位等信息)
  • 然后JVM会使用CAS(Compare and Swap)操作尝试将对象头中的偏向锁替换为指向锁记录的指针,并将线程ID记录到锁记录当中
  • 如果CAS操作成功,表示锁升级成功,当前线程进入临界区指向代码;如果失败,则说明有其他线程竞争,可能进一步升级为重量级锁

轻量级锁到自旋锁的状态:

  1. 初始状态:假设有两个线程A和B,线程A先获取到了对象obj的轻量级锁,并且正在执行临界区代码。
  2. 竞争锁:线程B也想要获取对象obj的锁,但发现轻量级锁已经被线程A获取了,此时线程B会进入自旋锁状态。
  3. 自旋锁等待:线程B进入自旋锁状态后,会进行一定次数的自旋(即循环重试),不断检查轻量级锁的标记是否被线程A释放。
  4. 自旋锁优化:在自旋锁状态下,如果线程A在很短的时间内释放了轻量级锁,线程B可以立即获取到锁而无需进入阻塞状态。这种情况下,自旋锁避免了线程上下文切换的开销,提高了性能。
  5. 自旋锁等待超时:如果线程B在自旋一定次数后仍然无法获取到锁,就会认为竞争激烈,并且有其他线程执行临界区代码的可能性不大。此时,线程B会放弃自旋锁而进入阻塞状态,让出CPU执行权,等待锁释放。
  6. 自旋锁的优势:自旋锁在临界区代码执行时间短、线程竞争不激烈的情况下可以提高性能,因为避免了线程上下文切换。但如果临界区代码执行时间长,或者线程竞争激烈,使用自旋锁可能会浪费CPU资源。

自旋锁升级为重量级锁:

        当一个线程在自旋锁状态下无法获取到锁时,它会升级到重量级锁。重量级锁是一种阻塞锁,它会导致线程进入阻塞状态并释放CPU的执行权。

下面是自旋锁升级到重量级锁的过程:

  1. 自旋锁等待:假设有两个线程A和B,线程A持有了对象obj的自旋锁,并且正在执行临界区代码。线程B尝试获取锁但失败,进入自旋状态。
  2. 自旋锁等待超时:线程B在经过一定次数的自旋后,仍然无法获取到锁。此时,线程B会放弃自旋锁,并将自己挂起,进入阻塞状态。
  3. 锁升级:线程B进入阻塞状态后,JVM会将锁升级为重量级锁。重量级锁的特点是会导致线程进入阻塞状态,并且释放CPU的执行权。
  4. 线程阻塞:线程B被阻塞后,它不会再去尝试获取锁,直到锁被释放。此时,线程B会进入一个等待唤醒的状态。
  5. 锁释放:当线程A执行完临界区代码后,会释放锁。此时,JVM会选择一个阻塞的线程(例如线程B)唤醒,使其重新尝试获取锁。
  6. 线程唤醒:被唤醒的线程(例如线程B)会从阻塞状态中恢复,并且开始重新尝试获取锁。

重量级锁的特点如下:

  • 高开销:使用重量级锁的线程会进入阻塞状态,这样会导致线程上下文切换的开销,影响性能。
  • 公平性:重量级锁通常是公平的,即等待时间最长的线程会被优先唤醒,以保证公平性。
  • 线程阻塞:获取重量级锁失败的线程会进入阻塞状态,不会消耗CPU资源,等待锁的释放。
  • 粒度较大:重量级锁对应整个对象而不是对象的某个部分,因此在多线程竞争情况下,会导致其他线程也无法访问对象。

        需要注意的是,升级到重量级锁的过程并非是一成不变的,具体实现可能会有不同的策略和优化。锁的具体行为和性能表现还取决于

猜你喜欢

转载自blog.csdn.net/miles067/article/details/132757278