一、偏向锁加锁

1、通过CAS操作将当前线程的地址设置到锁对象的markword中。如果设置成功了,那么就是设置偏向锁成功了

2、在当前线程的栈贞中,创建锁记录(Lock Record),使这个锁记录锁标识指向锁对象。这个是不需要CAS操作的。

二、偏向锁解锁

注:不会主动进行解锁,出现竞争时才会解锁,这样做的目的是下一次同一个线程来获取锁时,直接检查mark word的锁记录就可以了。

过程:在B线程获取偏向锁时,查看mark word的线程id不是自己的,那么B线程就会向VM的线程队列发送一个撤销偏向锁的任务,VM线程会不断检测是否有任务要执行,当检测到这个任务后,就需要在安全点去执行(安全点时,JVM内的所有线程都会被阻塞,只有VM线程处于运行状态,它可以执行一些特殊的任务,如full gc就是此时执行)

注:因为撤销锁的时候,会修改持有锁线程的栈数据,如果不在安全点执行的话,会有并发问题。

1、需要等待全局安全点(在这个时间点上没有正在运行的字节码)。它会首先暂停拥有偏向锁的线程,然后检测这个线程是否存活

2、如果不存活的话,那么就先将对象头设置为无锁状态,并偏向提交撤销锁的那个线程。

3、如果是存活状态,

  • 存在竞争:那么就先将对象头设置为无锁状态,并升级为轻量级锁

这里的竞争是通过查看锁记录中,是否还有指向锁对象的记录,存在表示还在同步代码块中,即上一个持有锁的线程还没有执行完。

  • 没有竞争:那么就先将对象头设置为无锁状态,并偏向另一个线程。

4、当偏向锁的撤销次数超过40次后,会直接升级为轻量级锁。

注:偏向锁升级的过程

1、遍历栈贞中锁记录,找到第一条记录,修改这条记录的displaced数据为锁对象的无锁状态mark word值

2、修改锁对象mark word的状态为轻量级锁状态,并且保留其中的线程内存地址。

三、轻量级锁加锁

1、在当前栈贞中创建一条锁记录,锁记录内的锁引用字段保存锁对象的地址

2、锁对象生成一条无锁状态的markword值,学名叫displacedMarkWord,然后将这个值保存到锁记录的displaced字段内

3、使用CAS去设置当前markword的值,修改为当前线程持有轻量级锁状态。

  • 更新为轻量级状态成功,那么就是获取轻量级锁成功
  • 如果失败,那么就会检测为什么失败,第一个要检测的就是是否是锁重入,检测到锁对象的markWord中锁持有者指向当前的线程,那么就是锁重入。这时只需要把在栈中插入的锁记录的displaced字段置为空就可以了。(即只有第一次加轻量级锁时,锁记录的displaced才有值)
  • 每次锁重入,都会记录锁记录,可以用来统计重入次数。

四、轻量级锁解锁

1、检测最后一条锁记录,会先将栈贞中锁记录的锁对象引用设置为null

2、检测这条锁记录的displaced字段是否有值,没有值证明这时一次锁重入,那么清除掉这条记录就可以了,步骤就是上面的将锁记录的锁对象的引用设置为null

3、每一次重入锁的释放都是这样的操作

4、然后就是最后一条锁记录的释放,这条锁记录是有displaced值的,这一步的释放是通过CAS将锁记录中的displaced值复制到锁对象中。

5、如果成功就是释放了,如果失败就有可能是锁已经膨胀了或者正在膨胀。

五、偏向锁的优势

1、使用Synchronized时,代码往往没有发生并发,偏向锁只在第一次获取时使用CAS,释放和重入都不需要CAS操作。而轻量级锁加锁和释放锁都需要CAS。

2、主要还是提供了一个可重入的环境,当个线程多次进行时,可以很快的执行。

3、它的指定条件其实是只有一个线程去获取

六、轻量级锁存在的意义

在大部分情况下,使用Synchronized执行通过代码块的时候,是没有并行的运行时环境的,都是线程的交替执行,如果直接升级成重量级锁的话,是没有必要的。

注:轻量级锁并不提供线程的互斥性。它的指定条件是多个线程交替去获取

注:偏向锁存在竞争的时候,会先撤销到无锁的状态,之后升级为轻量级锁,而轻量级锁升级时直接升级成重量级锁。

七、什么时候膨胀到重量级锁

1、有线程调用轻量级锁或偏向锁对象的hashcode方法

因为这个时候mark word是没有办法存储hash值的,所以需要膨胀到重量级锁

2、持锁的线程调用锁对象的wait()方法

因为锁对象处于偏向或者轻量级锁的状态下,是没有管程对象和等待队列的,所以无法保存线程节点

3、轻量级锁状态发生并发时,会膨胀成重量级锁。

八、重量级锁

1、重量级锁的ObjectMonitor结构

偏向锁也会创建lock record吗 偏向锁加锁过程_等待队列

主要结构:

  • ContentionList:等待队列
  • EntryList:存放等待锁而被block的线程队列
  • WaitSet:线程调用await()方法后,则线程会加入到waitSet中
  • Owner:表示当前持有锁的线程

2、过程:

(1)膨胀到重量级锁之后,当一个线程想要获取锁的时候,先去锁对象的mark word中获取管程对象的地址,进入管程后先尝试几次获取锁,就是通过CAS将管程内的owner字段设置为当前线程。设置成功就是获取锁成功了,没成功的话就是有占用。

(2)有占用时先封装成一个ObjectWaiter对象,存入到EntryList的队首,然后调用park挂起当前线程。底层就是通过Linux的mutex互斥量来实现

(3)当线程释放锁的时候,会从等待队列或者EntryList中选择一个线程进行唤醒,被选中的线程叫假定继承人,因为在它获取锁的过程中,外部线程有机会获取到锁,这也是Synchronized是不公平锁的原因。

(4)当持锁的线程调用await()方法时,会将线程加入到waitSet中,当被Object.notify唤醒后,会从waitSet移动到EntryList,继续等待获取锁。

九、重量级锁释放锁

1、设置owner为null,这个时候其他线程可以获取锁,所以是不公平的。

2、如果没有等待的线程则直接返回就可以了

3、如果有的话,就根据不同的策略去唤醒线程,唤醒的线程重新尝试去获取锁,即入等待队列和EntryList..

注:这个唤醒策略很多,默认的情况下是,从EntryList中获取,如果EntryList没有线程,那么就从等待队列中按原有顺序插入到EntryList中,这就意味着先插入到等待队列的线程,在EntryList中是最后被唤醒的

十、轻量级锁升级为重量级锁

1、轻量级锁发生竞争时会进行锁膨胀,B线程会先在锁对象中设置一个空闲的管程对象,再然后通过CAS修改锁对象状态为膨胀中,如果失败的话证明已经在膨胀了,直接通过自旋进行获取就可以了。

2、如果成功了,就将管程对象的owner设置为原持有轻量级锁的线程,再将第一条锁记录的displaced数据保存到管程对象中,可能是为了锁降级使用(说是能降级,但是不知道是不是真的能降级)

3、设置锁对象mark word为重量级锁。之后线程来获取锁,就走重量级锁的步骤了。