JUC-Synchronized原理(2)

194 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Synchronized是有一个锁升级过程的,分别是无锁、偏向锁、轻量级锁、重量级锁

偏向锁

我把偏向锁看成是一种对轻量级锁的优化

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。而偏向锁在进入之前可以判断进程id是否是自己,如果是则不用进行CAS操作

偏向锁使用: 以先判断锁标志,再判断偏向锁标志位,只有最后三位是 101才开始,否则直接走其他的锁。

  • 如果是匿名状态,线程ID为0,采⽤CAS去将当前线程写入,如果成功则获得锁,不成功表示存在竞争。
  • 线程ID不为0,此前已经有偏向,判断此值是否和当前线程相同,若⼀致则表示线程之前就获得了锁,不⼀致就尝试CAS替换。

如何撤销偏向锁:

  1. 如果有线程想要竞争该锁,就会撤销偏向锁
  2. 调用该对象的hashcode方法
  3. 调用wait/ notify方法

偏向锁的优缺点

  • 优点:只有单一线程访问对象的时候,偏向锁性能好,只在第一次需要CAS操作
  • 缺点:当发生竞争时,竞争需要等到安全点并且进行一系列比较,比较耗费时间。另外,当程序需要Hash值时,会调用HashCode方法,这会导致偏向锁退出

轻量级锁

  • 轻量级锁设计的初衷是在没有多线程竞争的前提 下,减少传统的重量级锁的资源损耗
  • 当锁标志位是00时,虚拟机栈就会开辟Lock Record空间

轻量级锁的使用

不再像偏向锁一样用线程id辩识,而是在当前线程的栈帧中建立⼀个指针,指向Lock Record空间

此指针空间包含两部分:

  1. displaced mark word:⽤于存储锁对象目前的Mark Word的拷贝副本
  2. owner:指向当前的锁对象的指针

虚拟即首先会将对象头的Mark World拷贝到栈帧中的Lock Record,如果操作成功则代表拥有了这个对象锁,如果操作失败则可能升级为重量级锁

轻量级锁的释放

使⽤CAS尝试将Lock Record中的displaced mark word替换回去,需要检查对象头中的指针是否依旧指向当前线程。 如果替换成功,表示没有竞争,锁成功释放

轻量级锁的重入

轻量级锁的每⼀次重入,都会在栈中⽣成⼀个Lock Record。第一次加锁时会拷贝Mark Word,Owner区指向对象头。后面的加锁(重入)并不会拷贝,仅仅是Owner指向对象本身, 每加⼀次锁帧栈中多⼀个Lock Record

自旋锁

  • 可以把自旋锁看成对锁膨胀的一种优化,每次轻量级锁发送竞争就进入重量级锁的话,很浪费性能,这时候先让线程多自旋几次,说不定这过程中,持有锁的线程也执行完毕了。
  • 自旋后之后仍未获得锁或者自旋的锁超过1个,那么会升级为重量级锁
  • JDK1.6之后加入了自适应自旋

重量级锁

重量级锁的实现依赖于ObjectMonitor,⽽ObjectMonitor⼜依赖于操作系统底层的Mutex Lock(互斥锁)实现

每个对象都可以关联一个Monitor对象 (又叫管程、监视器) ,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针

当线程访问同步代码块时,每个线程都会被封装成⼀个ObjectWaiter对象进⼊monitor

持有同一个Monitor的对象会按以下规则运行:

  • 正在执行的线程称为Owner,如果这时候有另一个线程想要进入临界区,就会被存放在EntryList中,进行阻塞
  • 当Owner线程执行完毕后,会唤醒EntryList的线程(非公平竞争)
  • WaitSet是之前获得到锁,但条件不满足进入Waiting状态的线程

重量级的优缺点

  • 缺点:重量级锁需要调用内核空间,产生较大开销。
  • 优点:重量级锁可以应对竞争激烈的场景