当多个处理器同时处理的时候,一般须要处理互斥的问题。
通常的解决方式都会包含acquire
和release
这个两种操做,操做保证,一个线程在acquire执行以后,在它执行release以前,其它线程不能完成acquire操做。这个过程常常就涉及到锁。研究代表(L. Lamport A fast mutual execlusion algorithm),经过 fast locks算法能够作到,lock和unlock操做所需的时间与潜在的竞争处理器数无关。
java内置了monitor来处理多线程竞争的状况.java
一种优化方式是使用 轻量锁来在大多数状况下避免重量锁的使用,轻量锁的主要机制是在monitor entry的时候使用原子操做,某些退出操做也是这样,若是有竞争发生就转而退避到使用操做系统的互斥量算法
轻量锁认为大多数状况下都不会产生竞争
在锁的使用中通常会使用几种原子指令: - CAS:检查给定指针位置的值和传入的值是否一致,若是一致,就修改 - SWAP:替换指针原位置的值,并返回旧的值 - membar:内存屏障约束了处理器在处理指令时的重排序状况,好比禁止同读操做被重排序到写操做以后 Java中使用 two-word 对象头 1. 是 mark word,它包括同步信息,垃圾回收信息、hash code信息 2. 指向对象的指针对象 这些指令的花销很昂贵,由于他们的实现一般会耗尽处理器的重排序缓冲区,从而限制了处理器本来可以像流水线同样处理指令的能力。研究数据发现(Eliminating_synchronization-related_atomic_operations_with_biased_locking_and_bulk_rebiasing)原子操做在真实的应用中,好比javac ,会致使性能降低20%。 > [此处2006年的文章第4段](https://blogs.oracle.com/dave/biased-locking-in-hotspot)大概说CAS和fence在操做系统中是序列化处理的,而序列化指令会使CPU几乎中止,终止并禁止任何无需指令,并等待本地存储耗尽。在多核处理器上,这种处理会致使至关大的性能损失
线程指针是NULL(0)表示当前没有线程被偏向这个对象
当分配一个对象而且这个对象可以执行偏向的时候而且尚未偏向时,会执行CAS是的当前线程ID放入到mark word的线程ID区域。安全
若是成功,对象自己就会被偏向到当前线程,当前线程会成为偏向全部者数据结构
线程ID直接指向JVM内部表示的线程;java虚拟机中则是在最后3bit填充0x5表示偏向模式。
若是CAS失败了,即另外一个线程已经成为偏向的全部者,这意味着这个线程的偏向必须撤销。对象的状态会变成轻量锁的模式,为了达到这一点,尝试把对象偏向于本身的线程必须可以操做偏向全部者的栈,为此须要全局安全点已经触达(没有线程在执行字节码)。此时偏向拥有者会像轻量级锁操做那样,它的堆栈会填入锁记录,而后对象自己的mark word会被更新成指向栈上最老的锁记录,而后线程自己在安全点的阻塞会被释放多线程
若是没有被原有的偏向锁持有者持有,会撤销对象从新回到可偏向可是尚未偏向的状态,而后尝试从新获取锁。若是对象当前锁住了是进入轻量锁,若是没有锁住是进入未被锁定的,不可偏向对象
下一个获取锁的操做会与检测对象的mark word,若是对象是可偏向的,而且偏向的全部者是当前那线程,会没有任何额外操做而立马获取锁。oracle
这个时候偏向锁的持有者的栈不会初始化锁记录,由于对象偏向的时候,是永远不会检验锁记录的
unlock的时候,会测试mark word的状态,看是否仍然有偏向模式。若是有,就不会再作其它的测试,甚至不须要管线程ID是否是当前线程IDapp
这里经过解释器的保证monitorexit操做只会在当前线程执行,因此这也是一个不须要检查的理由
经验发现为特定的数据结构选择性的禁用偏向锁(Store-fremm biased lock SFBL)来避免不合适的状况是合理的。为此须要考虑每一个数据结构究竟是执行撤销偏向的消耗小仍是从新回到可偏向的状态消耗下。一种启发式的方式来决定究竟是执行那种方式,在每一个类的元数据里面都会包含一个counter和时间戳,每次偏向锁的实例执行一次偏向撤销,都会自增,时间戳用于记录上次执行bulk rebias的时间。post
撤销计数并统计那些处于可偏向可是未偏向状态的撤销,这些操做的撤销只须要一次CAS就能够
counter自己有两个阈值,一个是bulk rebias阈值,一个是bulk revocation。刚开始的时候,这种启发式的算法能够单独的决定执行rebias仍是revoke,一单bulk rebias的阈值达到,就会执行bulk rebias,转移到 rebiasable状态
time阈值用来重置撤销的计数counter,若是自从上次执行bulk bias已经超过了这个阈值时间,就会发生counter的重置。性能
这意味着从上次执行bulk rebias到如今并无执行屡次的撤销操做,也就是说执行bias仍然是个不错的选择
可是若是在执行了bulk rebias以后,在时间阈值以内,仍然一直有撤销数量增加,一旦达到了bulk revocation的阈值,就会执行bulk revocation,此时这个类的对象不会再被容许使用偏向锁。测试
Hotspot中的阈值以下 Bulk rebias threshold 20 Bulk revoke threshold 40 Decay time 25s撤销偏向自己是一个消耗很大的事情,由于它必须挂起线程,遍历栈找到并修改lock records(锁记录)
最明显的查找某个数据结构的全部对象实例的方式就是遍历堆,这种方式在堆比较小的时候还能够,可是堆变大就显得性能很差。为类解决这个为题,使用 epoch
。
epoch是一个时间戳,用来代表偏向的合法性,只要这个数据接口是可偏向的,那么就会在mark word上有一个对应的epoch bit位
这个时候,一个对象被认为已经偏向了线程T必须知足两个条件,1: mark word中偏向全部这的标记必须是这个线程,2:实例的epoch必须是和数据结构的epoch相等
epoch自己的大小是限制的,也就是有可能出现循环,但这并不影响方案的正确性
经过这种方式,类C的bulk rebiasing操做会少去不少的花销。具体操做以下
这样就不用扫描堆了,对于那些没有被改变epoch的实例(和类的epoch不一样),会被自动当作可偏向可是尚未偏向的状态
这种状态可看作 rebiaseable
批量撤销自己存在着性能问题,通常的解决方式以下
容许锁具备永远改变(或者不多)的固定偏向线程,而且容许非偏向线程获取锁而不是撤销锁。
这种方式必须确保获取锁的线程必须确保进去临界区以前没有其它线程持有锁,而且不能使用 read-modify-write的指令,只能使用read和write
当前Hotspot JVM中的在32位和64位有不一样的形式
64bit为
32bit为
轻量锁(thin locks),细节如前所述。它在HotSpot中使用displaced header的方式实现,又被称做栈锁
mark完整的状态转换关系以下
此时有新线程来竞争
此时有新的线程来竞争,一种策略是使用启发式的方式来统计撤销的次数
对于通过bulk rebias的对象,检查期间没有锁定的实例,它的epoch会和class的不同,变成过时,可是能够偏向
5.1 若是 发生垃圾回收,lock会被初始化成可偏向但未偏向的状态(这也能够下降epoch循环使用的影响)
处于轻量锁状态,它可能没有hashcode计算,可能有,这依赖于inflat
处于重量锁状态
计算过hashcode,再加锁和解锁对应状态转换(9.10)
Eliminating_synchronization-related_atomic_operations_with_biased_locking_and_bulk_rebiasing
Evaluating and improving biased locking in the HotSpot virtual machine
biased-locking-in-hotspot