synchronized优化原理


一、Monitor

Monitor的工作原理也是synchronized底层原理
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁之后,该对象头的MarkWord中就被设置指向Monitor对象的指针

1.1 Monitor结构

在这里插入图片描述

  1. 当多个线程来执行synchronized临界区代码时,若Thread2抢占到了使用权,那么Monitor对象中的Owner就指向Thread2
  2. 其他线程执行到synchronized时,发现obj已经关联了一个Monitor锁,查看Monitor锁的Owner有没有上锁,那么此时Monitor中的Owner是指向Thread2的,所以其他线程获取不到锁,那么获取不到锁的线程会与Monitor锁中的EntryList(可以把EntryList理解为等待队列)进行关联
  3. 当Monitor锁中Owner的Thread2线程将synchronized临界区代码执行完毕时,那么Monitor锁中Owner就空余出来,并且会通知Monitor锁中EntryList中的等待线程,唤醒等待线程后会去竞争,去竞争synchronized临街区代码的执行权,并且将获取到synchronized临街区代码的执行权的线程放入Monitor锁中的Owner

二、轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化
轻量级锁对使用者是透明的,即语法仍然是synchronized

假设有两个方法同步块,利用同一个对象加锁

static final Object obj=new Object();
public static void method1(){
    
    
	synchronized(obj){
    
    
		//同步块A
		method2();
	}
	
}
public static void method2(){
    
    
	synchronized(obj){
    
    
		//同步块B

	}
}
  1. 创建锁记录(Lock Record)对象,它是JVM层面的,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
    状态00 可以理解为 轻量级锁
    状态01 可以理解为 正常状态(未加锁)
    在这里插入图片描述

  2. 当线程T0执行到method1方法给对象上锁后,让T0线程中的Object reference指向锁对象,并尝试CAS替换Object中的Mark Word,将Mark Word的值存入T0线程中的锁记录
    状态00 可以理解为 轻量级锁
    状态01 可以理解为 正常状态(未加锁)
    在这里插入图片描述

  3. 如果T0线程CAS替换成功,Object对象头中存储了T0线程的锁记录地址和状态00,表示由该线程给对象加锁,如图

    状态00 可以理解为 轻量级锁
    在这里插入图片描述

  4. 如果第3步CAS失败,有两种情况
    4.1 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程
    4.2 如果是自己执行了synchronized锁重入(可以理解为加锁的方法1,又调用了加锁的方法2,加锁的是同一个obj),那么再添加一条Lock Record作为重入的计数
    在这里插入图片描述

  5. 当退出synchronized代码块(解锁时),如果T0线程有取值为null锁记录,表示有重入,这时重置锁记录,表示重入计数减一
    在这里插入图片描述

  6. 退出synchronized代码块(解锁时)锁记录的值不为null,这时T0线程使用CASMark Word的值恢复给Object对象头
    6.1 成功,则解锁成功
    6.2 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

三、锁膨胀

如果在尝试加轻量级锁的过程中CAS操作无法成功,这是一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为`重量级锁``

static Object obj=new Object();
public static void method1(){
    
    
	synchronized(obj){
    
    
		//同步块
	}
	
}
  1. T1线程进行轻量级加锁时,T0线程已经对该对象加了轻量级锁
    此时Object中Mark Word的状态是10,代表的是重量级锁
    在这里插入图片描述
  2. 这时T1加轻量级锁失败,进入膨胀锁流程
    2.1 即为Object对象申请Monitor锁,让Object指向重量级锁地址
    2.2 然后自己进入Monitor的EntryList变成blocked阻塞状态
    在这里插入图片描述
  3. 当T0线程退出同步块解锁时(此时T0线程给对象加的是轻量级锁),使用CAS将Mark Word的值恢复给Object的对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的blocked线程

四、自旋优化

  1. 自旋成功情况:重量级锁竞争的时候,还可以采用CAS自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞
  2. 自旋失败情况:若线程执行synchronized同步块迟迟不解锁,B线程自旋重试几次之后,没有获取到锁,那么B线程就会进入Monitor的EntryList进入变成blocked状态

五、偏向锁

轻量级锁没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。在Java6中引入了偏向锁来进一步优化:只有第一次使用CAS将线程ID设置到Object的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有

static final Object obj=new Object();
public static void method1(){
    
    
	synchronized(obj){
    
    
		//同步块A
		method2();
	}
	
}
public static void method2(){
    
    
	synchronized(obj){
    
    
		//同步块B
		method3();
	}
}
public static void method3(){
    
    
	synchronized(obj){
    
    
		//同步块C
		
	}
}

图解:当没有竞争时,T0线程执行m1方法执行synchronized(obj),会将ThreadID替换Obejct对象头中的MarkWord,m1又调用了m2方法,m2执行synchronized(obj),会去检查Object对象头中的ThreadID是否是自己当前这个线程,m2又调用了m3方法,m3执行synchronized(obj),会去检查Object对象头中的ThreadID是否是自己当前这个线程,则就不需要进行CAS尝试替换
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_50677223/article/details/130714070
今日推荐