本文主要是对synchronized使用各个情况,加解锁底层原理的讲解
一,重量级锁
对象头
讲重量级锁之前,先了解一下一个对象的构成,一个对象是由对象头和对象体组成的,本文主要讲对象头,对象体其实就是对象的成员变量,对象头由mark word 和klass word组成,klass word就是指当前对象的类型,而mark word在正常状态下就是指对象的hashcode,所处的年代,是否是偏向锁,加锁的状态,见下图
monitor(锁)--操作系统底层实现,不由我们直接控制
接下来正式进入重量级锁的讲解,首先每一个java对象都可以关联一个monitor对象,只要我们使用synchronized,就会将该对象的markwork设置为改monitor的指针地址,并设置状态为10,见上图的第四行
整个步骤为:
- 首先一开始monitor对象没有和任何对象做绑定,当第一次执行synchronized代码块时,就会将其对象与底层的monitor对象做绑定,将monitor对象的指针地址替换掉对象本身的markword
- 设置monitor对象的owner为当前线程的栈帧
- 当有第二个线程进入synchronized代码块时,首先判断当前对象的markword有没有指向monitor,没有的话,则执行前两步骤,否则去判断owner是否指向某个线程,是的话,则进入monitor的entrylist进入阻塞,等待owner执行完毕,不是的话,则执行1,2步骤
- 释放锁时,首先找到当前对象的引用地址,然后获取markword中的monitor对象地址,然后从monitor中清除掉owner指向的当前线程,然后从monitor中拿到对象正常状态下的markword进行重置,还原正常状态,然后唤醒entrylist中的所有线程,抢占时间按片重复上述步骤即可
- 当synchronized代码块出现异常也不用担心,同样会帮我们重置当前对象的状态,并唤醒entrylist中的线程,然后会抛出异常,避免死锁
二,轻量级锁
轻量级锁并不是用来替换重量级锁的,我们知道,monitor是操作系统提供给我们的对象,频繁的操作它加解锁实际上是一个很消耗性能的操作,当我们线程竞争没那么频繁的时候,比如我前一个线程执行完代码块,后一个线程才进入,那么我就没必要加锁,当竞争频繁的时候我们再升级为重量级锁
具体步骤如下:
- 首先当执行到 synchronized代码块时,会在当前线程栈中写入一个锁记录lock record,然后将当前对象的引用地址写入锁记录的object reference中
- 将当前对象的markword和锁记录的地址通过cas进行交换,状态切换为00就是轻量级锁,见图一的第三行,如果当前对象的状态为01则可以交换成功,表示当前对象由当前线程持有,如果为00则会交换失败,说明但前锁已经被别人持有了,这个时候又会分为两种情 况:
- 第一线程重入,就是说持有锁的是当前线程,那么因为又执行了synchronized代码块,左边的栈中又写入一条栈帧锁记录 ,但是因为替换失败所以锁记录的地址为null,当前线程锁次数加一,因为本身是当前线程获取到了锁,所以仍然可以进入代码块执行,当执行完里层的synchronized代码块,就会将当前线程锁的次数减一,当执行到最外层的代码块时判断如果锁记录有值说明是最后一层,并判断当前线程锁的次数,如果为0则释放锁;
- 第二种就是,之气获取到锁的不是当前线程,那么锁就会升级为重量锁(或者专业点说叫锁膨胀),并把对象的markword由锁记录地址替换为monitor对象的地址,并将monitor的owner指向之前线程的栈地址,将当前线程放入entrylist阻塞等待