一:悲观锁机制

在Java中,主要采用了两种实现方式:
1、基于Object的悲观锁。
2、基于CAS的乐观锁。

本文主要讲解基于Object的悲观锁。
尝试用一句话概括:在Java中,每个0bject,也就是每个对象都拥有一把锁,这把锁存放在对象头中,记录了当前对象被哪个线程占用。

1.1、Java对象头结构

Java对象分为三个部分:
对象头、实例对象、对齐填充字节

对象头包含两个部分:
Mark Word(存储了很多当前对象的运行时状态信息,比如HashCode、锁状态标志、指向锁记录的指针、偏向线程ID、锁标志位等等)
Class Pointer(Class Pointer是一个指针,指向当前对象类型所在方法区中的Class信息)

可以通过下面这张表对Mark Word有一个更直观的认识:
在这里插入图片描述

二:synchronized实现原理

在Java中,synchronized关键词可以用来同步线程,synchronized被编译后会生成monitorenter和monitorexit两个字节码指令,依赖这两个字节码指令来进行线程同步。
在这里插入图片描述
这就是synchronized关键字所实现的同步机制,但是synchronized可能存在性能问题,因为monitor的下层是依赖于操作系统的Mutex Lock来实现的。Java线程事实上是对操作系统线程的映射,所以每当挂起或唤醒一个线程都要切换到操作系统的内核态,这个操作是比较重量级的。在某些情况下,甚至切换时间本身就会超出线程执行任务的时间,这样的话,使用synchronized将会对程序的性能产生影响。

但是从Java6开始,synchronized进行了优化,引入了"偏向锁"、“轻量级锁"的概念。因此对象锁总共有四种状态,从低到高分别是"无锁”、“偏向锁”、“轻量级锁”、“重量级锁”,这就分别对应了Mark Word中锁标记位的四种状态。

2.1、锁的升级与对比

锁升级的过程是不可逆的
在这里插入图片描述

1、偏向锁

现在我们给对象开始加锁,假如一个对象被加锁了,但在实际运行时,只有一条线程会获取这个对象锁。那么我们最理想的方式,是不要通过系统状态切换,只在用户态把这件事做掉。我们设想的是,最好对象锁能够认识这个线程,只要是这个线程过来,那么对象就直接把锁交出去。我们可以认为这个对象锁偏爱这个线程,所以被称为"偏向锁"。
那么偏向锁是怎么实现的呢?
其实很简单,在Mark Word中,当锁标志位是01,那么判断倒数第三个bit是否为1,如果是1,代表当前对象的锁状态为偏向锁,于是再去读Mark Word的前23个bit,这23个bit就是线程ID,通过线程ID来确认想要获得对象锁的线程是不是"被偏爱的线程"。

假如情况发生了变化,对象发现目前不只有一个线程,而是有多个线程正在竞争锁,那么偏向锁将会升级为轻量级锁。

2、轻量级锁

当一个线程想要获得某个对象的锁时,假如看到锁标志位为00,那么就知道它是轻量级锁。这时,线程会在自己的虚拟机栈中开辟一块被称为"Lock Record"的空间。
Lock Record中存放什么呢?
存放的是对象头的Mark Word的副本以及Owner指针。线程通过CAS去尝试获取锁,一旦获得,那么将会复制该对象的Mark Word到虚拟机栈的Lock Record中,并且将Lock Record中的Owner指针指向该对象锁。另一方面,对象的Mark Word中的前30bit将生成一个指针,指向持有该对象锁的线程虚拟机栈中的Lock Record。这样一来就实现了线程和对象锁的绑定,它们因此互相知道对方的存在。

这时,这个对象被锁定了,获取了这个对象锁的线程就可以去执行一些任务。那么你肯定要问,这时候万一有其他线程也想要获取这个对象,怎么办呢?此时其他线程将会自旋等待(这种方式区别于被操作系统挂起阻塞,因为如果对象锁很快就会被释放的话,自旋去获得锁完全在用户空间解决,不需要进行系统中断和现场恢复,所以它的效率更高)。

顺便提一下,自旋相当于是CPU在空转,如果长时间自旋,将会浪费CPU资源,于是出现一种叫做"适应性自旋"的优化。简单来说就是自旋的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的状态来决定。比如在同一个锁上,当前正在自旋等待的线程刚刚成功获得过锁,但是锁目前被其他线程持有,那么虚拟机就会认为下次自旋很有可能会再次成功,进而它将允许更长的自旋时间。这就是"适应性自旋"。

假如对象锁被一个线程持有着,此时也有一个线程正在自旋等待,如果同时又有多个线程想要获取这个对象锁。也就是说,一旦自选等待的线程数超过1个,那么轻量级锁将会升级为"重量级锁"。

3、重量级锁

如果对象锁状态被标记为重量级锁,那么就和我最初讲的那样,需要通过Monitor来对线程进行控制,此时将会使用同步原语来锁定资源,对线程的控制也最为严格。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐