Hotspot concurrency implementation:修订间差异

来自WHY42
imported>Riguz
学习一下Hotspot中的锁实现。
 
Riguz留言 | 贡献
无编辑摘要
第4行: 第4行:
= Head word=
= Head word=


JVM 中对象头包含两部分信息,一个是Mark word,存储同步、GC等信息;另一部分是存该对象所属的类型的指针:
JVM 中对象头包含两部分信息,一个是<span class="article-label">Mark word</span>,存储同步、GC等信息;另一部分是存该对象所属的类型的指针:
 
[[File:https://wiki.openjdk.java.net/download/attachments/11829266/Synchronization.gif?version=4&modificationDate=1208918680000&api=v2|600px|Hotspot synchronization]]
 


[[File:Hotspot-Synchronization.gif|600px|Hotspot synchronization]]


= Hotspot锁实现=
= Hotspot锁实现=
第14行: 第12行:
对于一个未锁定的对象,如果不允许偏向,那么当线程尝试给这个对象加锁的时候,首先尝试使用轻量级锁定,步骤如下:
对于一个未锁定的对象,如果不允许偏向,那么当线程尝试给这个对象加锁的时候,首先尝试使用轻量级锁定,步骤如下:


* 首先在当前线程的桢栈中创建一个lock record信息,保存对象的mark word,然后尝试通过CAS把这个lock record的地址设置到对象的header word中
* 首先在当前线程的桢栈中创建一个<span class="article-label">lock record</span>信息,保存对象的<span class="article-label">mark word</span>,然后尝试通过CAS把这个<span class="article-label">lock record</span>的地址设置到对象的<span class="article-label">header word</span>中
* 如果CAS成功,那么当前线程成功获取到锁,这时候最后两位是00,表示对象被轻量级锁定
* 如果CAS成功,那么当前线程成功获取到锁,这时候最后两位是00,表示对象被轻量级锁定
* 如果CAS失败,这时候首先需要判断一下当前线程是否已经持有锁(存在当前线程递归获取锁的场景;但CAS比较的时候,比较的条件是按照对象未锁定的场景去比较的,所以即使对象已经被轻量级锁定了,在CAS之前根本没有去判断),如果是则表明当前线程已经取得锁,可以继续执行
* 如果CAS失败,这时候首先需要判断一下当前线程是否已经持有锁(存在当前线程递归获取锁的场景;但CAS比较的时候,比较的条件是按照对象未锁定的场景去比较的,所以即使对象已经被轻量级锁定了,在CAS之前根本没有去判断),如果是则表明当前线程已经取得锁,可以继续执行
第35行: 第33行:
</syntaxhighlight>
</syntaxhighlight>


而当线程运行完成之后,还需要把对象的mark word还原回去:
而当线程运行完成之后,还需要把对象的<span class="article-label">mark wor</span>d还原回去:


* 从线程的栈中获取原来的mark word,尝试使用CAS设置到对象上
* 从线程的栈中获取原来的<span class="article-label">mark word</span>,尝试使用CAS设置到对象上
* 如果成功,那么不需要做其他事情
* 如果成功,那么不需要做其他事情
* 如果失败,表明已经膨胀为重量级锁了,需要通知到等待线程
* 如果失败,表明已经膨胀为重量级锁了,需要通知到等待线程


对于同一个线程递归锁定的场景,如果上一步CAS失败发现已经被自己持有锁,这个时候在栈上的lock record中设置为0,个人理解是如果设置为0那么在解锁的时候,可以控制不采用CAS恢复对象mark word,而是等到第一个lock操作对应的unlock操作的时候去恢复。
对于同一个线程递归锁定的场景,如果上一步CAS失败发现已经被自己持有锁,这个时候在栈上的<span class="article-label">lock record</span>中设置为0,个人理解是如果设置为0那么在解锁的时候,可以控制不采用CAS恢复对象<span class="article-label">mark word</span>,而是等到第一个lock操作对应的unlock操作的时候去恢复。


== 偏向锁(Store-Free Biased Locking)==
== 偏向锁(Store-Free Biased Locking)==
轻量级加锁解决的问题就是,多个线程交替地去获取锁,但实际没有并发争用。在实际的软件中,还有许多场景是,一个对象在生命周期内由始至终只有一个线程会去锁定,那么,在这这种情况下是否可以避免反复的CAS操作,而是直接”偏向“让原来持有锁的线程获取锁呢?
轻量级加锁解决的问题就是,多个线程交替地去获取锁,但实际没有并发争用。在实际的软件中,还有许多场景是,一个对象在生命周期内由始至终只有一个线程会去锁定,那么,在这这种情况下是否可以避免反复的CAS操作,而是直接”偏向“让原来持有锁的线程获取锁呢?


Java对象初始化的时候的header word有会有一个是否允许偏向的标志位:
Java对象初始化的时候的<span class="article-label">header word</span>有会有一个是否允许偏向的标志位:


* 如果该类可以使用偏向锁,则对象包含thread id(初始化位0),biased_lock=1表示允许偏向
* 如果该类可以使用偏向锁,则对象包含<span class="article-label">thread id</span>(初始化位0),<span class="article-label">biased_lock=1</span>表示允许偏向
* 如果该类不可以使用偏向锁,则对象包含一个hash code,biased_lock被设置位0表示不允许偏向
* 如果该类不可以使用偏向锁,则对象包含一个<span class="article-label">hash code</span>,<span class="article-label">biased_lock</span>被设置位0表示不允许偏向


那么,在尝试加锁的过程中,如果发现允许偏向,则步骤如下:
那么,在尝试加锁的过程中,如果发现允许偏向,则步骤如下:


* 尝试通过CAS,将当前线程ID、epoch等替换到对象头中,这是唯一的一次CAS操作,称之为initial lock
* 尝试通过CAS,将当前线程ID、epoch等替换到对象头中,这是唯一的一次CAS操作,称之为<span class="article-label">initial lock</span>
* 当线程持有对象的偏向锁之后,后续该线程的加锁和解锁无需额外的CAS操作或者更新对象头
* 当线程持有对象的偏向锁之后,后续该线程的加锁和解锁无需额外的CAS操作或者更新对象头


而当一个线程尝试对一个偏向其他线程的对象加锁的时候,需要撤销偏向锁,并把现场恢复成好像是通过thin lock锁住这个对象一样。这时候进行的步骤如下:
而当一个线程尝试对一个偏向其他线程的对象加锁的时候,需要撤销偏向锁,并把现场恢复成好像是通过<span class="article-label">thin lock</span>锁住这个对象一样。这时候进行的步骤如下:


* 停止偏向锁持有线程到安全点
* 停止偏向锁持有线程到安全点
* 遍历偏向锁的持有线程的栈,调整lock record为thin lock的模式;并把最开始的lock record设置到对象的header中
* 遍历偏向锁的持有线程的栈,调整<span class="article-label">lock record</span>为<span class="article-label">thin lock</span>的模式;并把最开始的<span class="article-label">lock record</span>设置到对象的header中
* 恢复线程,按照thin lock的方式执行(包括膨胀机制)
* 恢复线程,按照<span class="article-label">thin lock</span>的方式执行(包括膨胀机制)




第76行: 第74行:
* https://www.javazhiyin.com/24364.html
* https://www.javazhiyin.com/24364.html
* https://pdfs.semanticscholar.org/b8e4/cb0c212fd799522817b914ffcd24470f707e.pdf?_ga=2.218049237.2144104280.1590746224-418849090.1590746224
* https://pdfs.semanticscholar.org/b8e4/cb0c212fd799522817b914ffcd24470f707e.pdf?_ga=2.218049237.2144104280.1590746224-418849090.1590746224
[[Category:Concurrency]]

2021年4月28日 (三) 13:34的版本

学习一下Hotspot中的锁实现。


Head word

JVM 中对象头包含两部分信息,一个是,存储同步、GC等信息;另一部分是存该对象所属的类型的指针:

Hotspot synchronization

Hotspot锁实现

轻量级加锁过程(thin lock)

对于一个未锁定的对象,如果不允许偏向,那么当线程尝试给这个对象加锁的时候,首先尝试使用轻量级锁定,步骤如下:

  • 首先在当前线程的桢栈中创建一个信息,保存对象的,然后尝试通过CAS把这个的地址设置到对象的
  • 如果CAS成功,那么当前线程成功获取到锁,这时候最后两位是00,表示对象被轻量级锁定
  • 如果CAS失败,这时候首先需要判断一下当前线程是否已经持有锁(存在当前线程递归获取锁的场景;但CAS比较的时候,比较的条件是按照对象未锁定的场景去比较的,所以即使对象已经被轻量级锁定了,在CAS之前根本没有去判断),如果是则表明当前线程已经取得锁,可以继续执行
  • 如果不是,那么说明有两个线程同时尝试锁定一个对象,这时候需要膨胀为重量级锁
// bytecodeInterpreter.cpp
 if (!success) {
   markOop displaced = rcvr->mark()->set_unlocked();
   mon->lock()->set_displaced_header(displaced);
   if (Atomic:: (mon, rcvr->mark_addr(), displaced) != displaced) {
     // Is it simple recursive case?
     if (THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
       mon->lock()->set_displaced_header(NULL);
     } else {
       CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
     }
   }
 }

而当线程运行完成之后,还需要把对象的d还原回去:

  • 从线程的栈中获取原来的,尝试使用CAS设置到对象上
  • 如果成功,那么不需要做其他事情
  • 如果失败,表明已经膨胀为重量级锁了,需要通知到等待线程

对于同一个线程递归锁定的场景,如果上一步CAS失败发现已经被自己持有锁,这个时候在栈上的中设置为0,个人理解是如果设置为0那么在解锁的时候,可以控制不采用CAS恢复对象,而是等到第一个lock操作对应的unlock操作的时候去恢复。

偏向锁(Store-Free Biased Locking)

轻量级加锁解决的问题就是,多个线程交替地去获取锁,但实际没有并发争用。在实际的软件中,还有许多场景是,一个对象在生命周期内由始至终只有一个线程会去锁定,那么,在这这种情况下是否可以避免反复的CAS操作,而是直接”偏向“让原来持有锁的线程获取锁呢?

Java对象初始化的时候的有会有一个是否允许偏向的标志位:

  • 如果该类可以使用偏向锁,则对象包含(初始化位0),表示允许偏向
  • 如果该类不可以使用偏向锁,则对象包含一个被设置位0表示不允许偏向

那么,在尝试加锁的过程中,如果发现允许偏向,则步骤如下:

  • 尝试通过CAS,将当前线程ID、epoch等替换到对象头中,这是唯一的一次CAS操作,称之为
  • 当线程持有对象的偏向锁之后,后续该线程的加锁和解锁无需额外的CAS操作或者更新对象头

而当一个线程尝试对一个偏向其他线程的对象加锁的时候,需要撤销偏向锁,并把现场恢复成好像是通过锁住这个对象一样。这时候进行的步骤如下:

  • 停止偏向锁持有线程到安全点
  • 遍历偏向锁的持有线程的栈,调整的模式;并把最开始的设置到对象的header中
  • 恢复线程,按照的方式执行(包括膨胀机制)


Reference:

http://gee.cs.oswego.edu/dl/jmm/cookbook.html