轻量锁 偏向锁

来源:互联网 发布:现代软件学院马少红 编辑:程序博客网 时间:2024/04/29 15:11
偏向锁   http://blog.163.com/silver9886@126/blog/static/35971862201472274958280/
Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用)
因此 流程是这样的 偏向锁->轻量级锁->重量级锁

锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,一字宽等于四字节,即32bit。
机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出syncronized块时,计数器减1,当计数器为0时,锁被释放(这就保证了锁是可重入的,不会发生死锁的情况)。

偏向锁,简单的讲,就是在锁对象的对象头中有个ThreaddId字段,这个字段如果是空的,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。
但是偏向锁也有一个问题,就是当锁有竞争关系的时候,需要解除偏向锁,使锁进入竞争的状态。
具体图片如下:
java 偏向锁 - silver9886@126 - silver9886@126的博客
偏向锁的抢占,其实就是两个进程对锁的抢占,在synchrnized锁下表现为轻量锁方式进行抢占。
java 偏向锁 - silver9886@126 - silver9886@126的博客
 
 注意这时只是偏向锁的释放,之后会进入到轻量级锁阶段,两个线程进入锁竞争状态,一个具体例子可以参考synchronized锁机制。
注:synchronized锁流程如下(转自http://xly1981.iteye.com/blog/1766224):
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” 
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。 
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord, 
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋 
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败 
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己
 
总结:
偏向锁,其实是无锁竞争下可重入锁的简单实现

参考资料:
1.http://xly1981.iteye.com/blog/1766224
2.Java SE1.6中的Synchronized  http://www.dedecms.com/knowledge/program/jsp-java/2012/0808/4386.html
3.Java偏向锁实现原理 http://kenwublog.com/theory-of-java-biased-locking

 轻量锁

http://blog.sina.com.cn/s/blog_c038e9930102v2ht.html

轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行(CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销)。

轻量级锁(LightweightLocking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。

它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救

轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为DisplacedMark Word。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋获取锁

轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。

 

 Java锁Synchronized之轻量锁

Java锁Synchronized之轻量锁

 在图中,提到了拷贝锁对象头的objectmark word,由于脱离了原始markword,官方将它冠以displaced前缀,即displacedmark word(置换标记字)。

这个displacedmark word是整个轻量级锁实现的关键,在CAS中的compare就需要用它作为条件。

为什么要拷贝mark word?其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。

在拷贝完object markword之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。

============

将object mark word里的轻量级锁指针指向lockrecord所在的stack指针,作用是让其他线程知道,该objectmonitor已被占用(就像偏向锁中用CAS的方式将markword的id指向当前尝试获取锁的线程id,这里是将markword中的轻量级锁指针以CAS的方式尝试指向当前线程的lockrecord,这样别的线程便知道当前轻量锁已经指向别的线程了)。

lock record里的owner指针指向objectmark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了

CAS 的是makeworld中的轻量级锁指针

================


下图直观地描述了交换指针的操作。

Java锁Synchronized之轻量锁

  Java锁Synchronized之轻量锁

最后一步unlock中,我们发现,JVM同样使用了CAS来验证object markword在持有锁到释放锁之间,有无被其他线程访问。

如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而markword的重量级锁指针也会被相应修改

释放锁线程视角所以由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。

因为重量级锁被修改了,所有display mark word和原来的markword不一样了。

怎么补救,就是进入mutex前,compare一下obj的markword状态。确认该markword是否被其他线程持有。

此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。

尝试获取锁线程视角:如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword修改重量级锁,表示该进入重量锁了。

还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword

这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码。

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。


0 0
原创粉丝点击