跳至主要內容

偏向锁

zheng大约 4 分钟java基础

0、从偏向锁到重量锁

在java同步代码快中,synchronized的使用方式无非有两个 :   

1)通过对一个对象进行加锁来实现同步
synchronized(lockObject){
     //代码
 }
 2)对一个方法进行synchronized声明,进而对一个方法进行加锁来实现同步。
public synchornized void test(){
     //代码
 }
 无论是对一个对象进行加锁还是对一个方法进行加锁,实际上,都是对对象进行加锁

1、先了解一下对象在JVM内存中的布局,如下图

img
img
    Mark Word:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。在32位系统占4字节,在64位系统中占8字节;

     Class Pointer:用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节;

     Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节;

     对齐填充:Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。

    从上图我们可以看出,对象中关于锁的信息是存在Markword里的。

2、锁的创建

// 随便创建一个对象
LockObject lockObject = new LockObject();
 synchronized(lockObject){
     //代码
 }
1)当我们创建一个对象LockObject时,该对象的部分Markword关键数据如下。
1571144064662
1571144064662
     从图中可以看出,偏向锁的标志位是“01”,状态是“0”,表示该对象还没有被加上偏向锁。(“1”是表示被加上偏向锁)。

     该对象被创建出来的那一刻,就有了偏向锁的标志位,这也说明了所有对象都是可偏向的,但所有对象的状态都为“0”,也同时说明所有被创建的对象的偏向锁并没有生效。

2)不过,当线程执行到临界区(critical section)时,此时会利用CAS(Compare and Swap)操作,将线程ID插入到Markword中,同时修改偏向锁的标志位。

      此时的Mark word的结构信息如下:
1571144092579
1571144092579
      此时偏向锁的状态为“1”,说明对象的偏向锁生效了,同时也可以看到,哪个线程获得了该对象的锁。   

3)这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。

4)在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行加锁或者解锁操作,而是会做以下的步骤:

     a、Load-and-test,也就是简单判断一下当前线程id是否与Markword当中的线程id是否一致.
     b、如果一致,则说明此线程已经成功获得了锁,继续执行下面的代码.
     c、如果不一致,则要检查一下对象是否还是可偏向,即“是否偏向锁”标志位的值。
     d、如果还未偏向,则利用CAS操作来竞争锁,也即是第一次获取锁时的操作。

5)如果此对象已经偏向了,并且不是偏向自己,则说明存在了竞争。此时可能就要根据另外线程的情况,可能是重新偏向,也有可能是做偏向撤销,但大部分情况下就是升级成轻量级锁了。可以看出,偏向锁是针对于一个线程而言的,线程获得锁之后就不会再有解锁等操作了,这样可以省略很多开销。假如有两个线程来竞争该锁话,那么偏向锁就失效了,进而升级成轻量级锁了。

6)为什么要这样做呢?因为经验表明,其实大部分情况下,都会是同一个线程进入同一块同步代码块的。这也是为什么会有偏向锁出现的原因。在Jdk1.6之后,偏向锁的开关是默认开启的,适用于只有一个线程访问同步块的场景

上次编辑于:
贡献者: 郑天祺