文章目录
- 四种锁的级别
- 四种锁介绍
- 锁升级过程
-
- 转换过程
- 轻量级锁的加锁过程
- CAS
四种锁的级别
JDK1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁。但是在JDK1.6后,JVM为了提高锁的获取与释放效率对synchronized 进行了优化,引入了偏向锁和轻量级锁,从此以后锁的状态就有了四种:无锁、偏向锁、轻量级锁、重量级锁。并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,这四种锁的级别由低到高依次是:无锁、偏向锁、轻量级锁、重量级锁。
这里需要说一下锁存放的位置:
锁标记存放在Java对象头的Mark Word位置。
32 位中:
在64位 虚拟机中:
它的最后2bit存放的是锁状态的标志位,用来标志当前对象的状态,对象所处的状态,决定了markword存储的内容。
四种锁介绍
-
无锁
无锁是指没有对资源的锁定,所有线程都能访问并修改同一资源,但同时只有一个线程修改成功。 -
偏向锁
初次执行到synchronized 代码块的时候,锁对象变成偏向锁(通过CAS修改对象头中的标志位),字面意思是“偏向于第一个获得它的锁”,执行完同步代码块,线程并不会自动释放偏向锁,当同一个线程第二次访问的时候,就会判断对象头中的线程是否为自己(获得锁之后,会在Mark Word 里存储当前线程的ID),不需要重新加锁,没有额外开销,性能高。
那什么时候释放锁呢? 当有其他线程尝试竞争偏向锁的时候,持有偏向锁的线程才会释放锁,注意线程是不会主动释放锁的。关于偏向锁的撤销,需要等到全局安全点,即在某个时间点上没有字节码正在执行时,它会暂停拥有偏向锁的线程,然后判断锁对象是否处于锁定状态。如果不处于活动状态则撤销,恢复到无锁或者轻量级锁状态。 -
轻量级锁
轻量级锁是指锁是偏向级锁的时候,却被另外的线程锁访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
获取的主要两种方式:
- 当关闭偏向锁时
- 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁 -
重量级锁
当线程锁竞争情况严重,某个达到最大自旋次数的线程(默认为10次),会将轻量级锁升级为重量级锁(通过CAS修改所标志位,但不修改持有ID)。当后序的线程尝试获取锁时,就将自己挂起,等待被唤醒。
重量级锁将控制权交给了操作系统,有操作系统来负责线程间的调度和状态变换,会出现频繁的对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源。
锁升级过程
转换过程
当线程来访问一个对象,锁的升级过程如下:
- 检查Mark Word 里面的线程ID,如果是,表示当前线程处于偏向锁
- 如果不是,将使用CAS将当前线程的ID替换Mard Word,如果成功,则表示当前线程获取偏向锁,置偏向锁的标志位为1
- 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
- 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,但前线程获得锁
- 如果失败,则通过自旋来尝试继续获得锁。
- 如果自旋成功,而仍然处于轻量级锁。
- 自旋失败,锁升级为重量级锁。
轻量级锁的加锁过程
-
虚拟机首先将在当前线程栈中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word
-
拷贝对象头中的Mark Word复制到锁记录中;
-
拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5
-
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态
-
如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态
CAS
最后简单的介绍一下CAS
在计算机科学中, 比较和交换 (Conmpare And Swap)是用于实现多线程同步的原子指令,是一种无锁原子算法。
CAS原理
-
它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。
-
操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成。
-
比较和交换 作为单个原子操作完成, 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。
仅当 V 值等于 A 值时,才会将 V 的值设为B,如果 V 值和A值不同,则说明已经有其他线程做了更新,则当前线程则什么都不做。最后,CAS 返回当前 V 的真实值。CAS 操作时抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会挂起,仅是被告知失败,并且允许再次尝试,当然也允许实现的线程放弃操作。 基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰。
参考链接:nowcoder