(一)synchronized关键字的底层原理
一、基本使用

二、Moniter
synchronized底层实际上就是Moniter。

为什么会释放两次对象锁,因为在底层使用了一个隐式的try…finally…,以防正常的代码在执行过程中抛异常,也就不走第一个解锁。
还需要研究一下 monitor的工作机制:

(1)Owner与EntryList
lock做为一个对象锁,首先会与monitor进行关联,并判断monitor的owner属性是否为null,若为null则会直接赋予lock对象锁给该线程,并且使owner属性关联本线程。此时线程二进来,发现owner已经绑定了线程一,线程二就不能再拥有该锁,要到EntryList中进行等待。即便有其他线程进来也是如此,且他们都会处于BLOCKED 阻塞状态。直到线程一执行完毕并释放锁,owner属性就会变回null并唤醒EntryList当中的线程,且让这些线程去争夺owner的拥有权(EntryList当中的线程没有先来后到的关系)。
(2)WaitSet
当某线程调用wait方法后,就会处于等待状态并放入WaitSet当中。
三、总结

(二)锁升级

用户态指的是自己编写的java代码,内核态指的是CPU层面的东西。这两者的资源权限不同,而用户态的权限较低,内核态的权限较高。因为JVM是系统级别的,属于内核态,而Monitor又是属于JVM来管理的,这中间涉及的切换的成本较高,而且进程的上下文切换的性能也较低。所以Monitor实现的锁的性能整体不高,也就称为重量级锁。
前面提到某线程在获取锁时,需要让对象锁与Monitor进行关联,但他们是如何关联上的呢?主要还是跟lock对象锁有关系。

一、Java对象的内存结构
在JVM内存结构中,我们创建的对象都存储在堆当中,HotSpot也就是JVM虚拟机的一种实现。
就是MarkWord描述了对象与monitor的关联关系,接下来将重点去讲解。
因为HotSpot虚拟机的内存管理系统,要求内存的起始地址必须为8的倍数,所以需要用到对齐填充。

二、MarkWord

ptr_to_heavyweight_monitor指的是指针,指向重量级锁所需要关联的monitor,实际上就是通过在此处记录monitor的地址,等于将对象与monitor进行关联。

三、轻量级锁
(1)加锁流程

当某线程进入同一把锁两次,就叫做锁重入。因为都是同一个线程持有的锁,所以就不存在竞争,也没必要使用monitor来实现,这时可以优先使用轻量级锁。
轻量级锁的工作原理:
当某线程将要去执行method1方法,此时就会创建一个锁记录Lock Record。每个线程的战争都包含了一个锁记录的结构,内部存储锁定对象的MarkWord。首先会让Object reference指定锁对象,而当当前线程持有锁时,会借助Lock Record(锁记录地址)去修改当前锁对象的MarkWord,这里主要使用CAS的方式去交换数据。
- CAS可以理解为保证在修改数据时是一个原子操作
- CAS交换成功
当CAS交换成功,就会在对象头存储锁记录的状态和状态,表示由该线程持有当前的对象锁。
CAS交换前:

CAS交换后:

- CAS交换失败
当CAS交换失败,可能是因为出现多个线程竞争,这时轻量级锁会直接升级为重量级锁;也可能是因为当前出现锁重入,这时就会在战争中再去添加一条Lock Record,做为重入计数,并且进行多次CAS交换数据,通过Lock Record的个数来表示当前线程重入了几次。

(2)解锁流程(synchronized代码块执行完毕)
- 首先判断当前的锁记录是否为null,若为null则表明存在锁重入,就需要删去当前的锁记录,代表一处synchronized代码块执行完毕。

- 若当前锁记录不为null,就会再次执行CAS操作,把对象头原先的值交换回来,这样就算解锁成功。

(3)总结

四、偏向锁

偏向锁的执行流程:
- 首次加锁
这次锁记录地址与对象的MarkWord需要进行CAS交换的数据内容不一样,会直接将当前线程的id写入,并将偏向锁标志改为1

- 重复加锁(锁重入)
首先也会去添加锁记录,但是这次不会再去执行CAS操作,只会去判断当前对象的MarkWord中的线程id是否为自己的,是的话只需记录当前的重入次数即可。

四、总结


3万+

被折叠的 条评论
为什么被折叠?



