
在实际场景中,如果一个同步块(或方法)没有多个线程竞争,而且总是由同一个线程多次重入获取锁,如果每次还有阻塞线程,唤醒CPU从用户态转为核心态,那么对于CPU是一种资源的浪费,为了解决这类问题,就引入了偏向锁的概念。
package com.shu.BiasedLocking;
import com.shu.Lock.ObjectLock;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.CountDownLatch;
/**
* @description:
* @author: shu
* @createDate: 2022/11/15 14:09
* @version: 1.0
*/
public class BiasedLockingDemo {
static final int MAX_TREAD = 10;
static final int MAX_TURN = 1000;
CountDownLatch latch = new CountDownLatch(MAX_TREAD);
public static void main(String[] args) throws InterruptedException {
System.out.println("JVM信息:\n"+VM.current().details());
//JVM延迟偏向锁
Thread.sleep(5000);
ObjectLock lock = new ObjectLock();
System.out.println("抢占锁前, lock 的状态: ");
lock.printObjectStruct();
Thread.sleep(5000);
CountDownLatch latch = new CountDownLatch(1);
Runnable runnable = () ->
{
for (int i = 0; i < MAX_TURN; i++) {
synchronized (lock) {
lock.increase();
if (i == MAX_TURN / 2) {
System.out.println("占有锁, lock 的状态: ");
lock.printObjectStruct();
//读取字符串型输入,阻塞线程
// Print.consoleInput();
}
}
//每一次循环等待10ms
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
latch.countDown();
};
new Thread(runnable, "biased-demo-thread").start();
//等待加锁线程执行完成
latch.await();
System.out.println("释放锁后, lock 的状态:");
lock.printObjectStruct();
}
}


05 10 28 26 (00000101 00010000 00101000 00100110) (640159749)
00 00 00 00 (00000000 00000000 00000000 00000000) (0)
转换一下小端序:
64 bit
(00000000 00000000 00000000 00000000 00100110 00101000 00010000 00000101)
我们参考之前MarkWord的偏向锁结构
解析字节
线程ID 54 bit 00000000 00000000 00000000 00000000 00100110 00101000 001000
epoch 2bit 00
未使用 1bit 0
分代年龄 4bit 0000
biased 1bit 1
locl 3bit 01

注意
虽然抢锁的线程已经结束,但是ObjectLock实例的对象结构仍然记录了其之前的偏向线程ID,其锁状态还是偏向锁状态101
JVM 安全点
OpenJDK官方定义如下:
引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗。重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗。



package com.shu.BiasedLocking;
import com.shu.Lock.ObjectLock;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.CountDownLatch;
/**
* @description: 轻量级锁
* @author: shu
* @createDate: 2022/11/16 11:16
* @version: 1.0
*/
public class LightWeightLockingDemo {
static final int MAX_TURN = 1000;
public static void main(String[] args) throws InterruptedException {
System.out.println("JVM信息:\n"+VM.current().details());
//JVM延迟偏向锁
Thread.sleep(5000);
ObjectLock lock = new ObjectLock();
System.out.println("抢占锁前, lock 的状态: ");
lock.printObjectStruct();
Thread.sleep(5000);
CountDownLatch latch = new CountDownLatch(2);
Runnable runnable = () ->
{
for (int i = 0; i < MAX_TURN; i++) {
synchronized (lock) {
lock.increase();
if (i == 1) {
System.out.println("第一个线程占有锁, lock 的状态: ");
lock.printObjectStruct();
}
}
}
//循环完毕
latch.countDown();
//线程虽然释放锁,但是一直存在
for (int j = 0; ; j++) {
//每一次循环等待1ms
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
new Thread(runnable).start();
Thread.sleep(1000);
Runnable lightweightRunnable = () ->
{
for (int i = 0; i < MAX_TURN; i++) {
synchronized (lock) {
lock.increase();
if (i == MAX_TURN / 2) {
System.out.println("第二个线程占有锁, lock 的状态: ");
lock.printObjectStruct();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
//循环完毕
latch.countDown();
};
new Thread(lightweightRunnable).start();
//等待加锁线程执行完成
latch.await();
Thread.sleep(2000); //等待2s
System.out.println("释放锁后, lock 的状态: ");
lock.printObjectStruct();
}
}
偏向锁状态
无竞争锁状态

存在锁竞争状态

(10001000 11110010 00010111 00100110)
(00000000 00000000 00000000 00000000)
转换为小端序 00000000 00000000 00000000 00000000 00100110 00010111 11110010 10001000
参考MarkWord 轻量级锁结构
ptr_to_lock_record 62bit 00000000 00000000 00000000 00000000 00100110 00010111 11110010 100010
lock 2bit 00
释放锁之后

普通自旋锁
自适应自旋锁
JDK 1.6的轻量级锁使用的是普通自旋锁,且需要使用-XX:+UseSpinning选项手工开启。JDK 1.7后,轻量级锁使用自适应自旋锁,JVM启动时自动开启,且自旋时间由JVM自动控制。
轻量级锁的本意是为了减少多线程进入操作系统底层的互斥锁(Mutex Lock)的概率,并不是要替代操作系统互斥锁。所以,在争用激烈的场景下,轻量级锁会膨胀为基于操作系统内核互斥锁实现的重量级锁。
在JVM中,每个对象都关联一个监视器,这里的对象包含Object实例和Class实例。监视器是一个同步工具,相当于一个许可证,拿到许可证的线程即可进入临界区进行操作,没有拿到则需要阻塞等待。重量级锁通过监视器的方式保障了任何时间只允许一个线程通过受到监视器保护的临界区代码。
ObjectMonitor:监视器是由C++类ObjectMonitor实现
//Monitor结构体
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
//线程的重入次数
_recursions = 0;
_object = NULL;
//标识拥有该Monitor的线程
_owner = NULL;
//等待线程组成的双向循环链表
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
//多线程竞争锁进入时的单向链表
cxq = NULL ;
FreeNext = NULL ;
//_owner从该双向循环链表中唤醒线程节点
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
Cxq、EntryList、WaitSet这三个队列的说明如下:
(1)Cxq:竞争队列(Contention Queue),所有请求锁的线程首先被放在这个竞争队列中。(2)EntryList:Cxq中那些有资格成为候选资源的线程被移动到EntryList中。
(3)WaitSet:某个拥有ObjectMonitor的线程在调用Object.wait()方法之后将被阻塞,然后该线程将被放置在WaitSet链表中。

EntryList与Cxq在逻辑上都属于等待队列。Cxq会被线程并发访问,为了降低对Cxq队尾的争用,而建立EntryList。在Owner线程释放锁时,JVM会从Cxq中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为OnDeck Thread(Ready Thread)。EntryList中的线程作为候选竞争线程而存在。
JVM不直接把锁传递给Owner Thread,而是把锁竞争的权利交给OnDeck Thread,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大地提升系统的吞吐量,在JVM中,也把这种选择行为称为“竞争切换”。
OnDeck Thread获取到锁资源后会变为Owner Thread。无法获得锁的OnDeck Thread则会依然留在EntryList中,考虑到公平性,OnDeck Thread在EntryList中的位置不发生变化(依然在队头)。在OnDeck Thread成为Owner的过程中,还有一个不公平的事情,就是后来的新抢锁线程可能直接通过CAS自旋成为Owner而抢到锁。
如果Owner线程被Object.wait()方法阻塞,就转移到WaitSet队列中,直到某个时刻通过Object.notify()或者Object.notifyAll()唤醒,该线程就会重新进入EntryList中。
package com.shu.BiasedLocking;
import com.shu.Lock.ObjectLock;
import org.openjdk.jol.vm.VM;
import java.util.concurrent.CountDownLatch;
/**
* @description: 重量级锁
* @author: shu
* @createDate: 2022/11/16 14:08
* @version: 1.0
*/
public class HeavyWeightLockingDemo {
static final int MAX_TURN = 1000;
public static void main(String[] args) throws InterruptedException {
System.out.println("JVM信息:\n"+VM.current().details());
//JVM延迟偏向锁
Thread.sleep(5000);
ObjectLock counter = new ObjectLock();
System.out.println("抢占锁前, counter 的状态: ");
counter.printObjectStruct();
Thread.sleep(5000);
CountDownLatch latch = new CountDownLatch(3);
Runnable runnable = () ->
{
for (int i = 0; i < MAX_TURN; i++) {
synchronized (counter) {
counter.increase();
if (i == 0) {
System.out.println("第一个线程占有锁, counter 的状态: ");
counter.printObjectStruct();
}
}
}
//循环完毕
latch.countDown();
//线程虽然释放锁,但是一直存在
for (int j = 0; ; j++) {
//每一次循环等待1ms
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
new Thread(runnable).start();
Thread.sleep(1000); //等待2s
Runnable lightweightRunnable = () ->
{
for (int i = 0; i < MAX_TURN; i++) {
synchronized (counter) {
counter.increase();
if (i == 0) {
System.out.println("占有锁, counter 的状态: ");
counter.printObjectStruct();
}
//每一次循环等待10ms
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
//循环完毕
latch.countDown();
};
new Thread(lightweightRunnable, "抢锁线程1").start();
Thread.sleep(100); //等待2s
new Thread(lightweightRunnable, "抢锁线程2").start();
//等待加锁线程执行完成
latch.await();
Thread.sleep(2000); //等待2s
System.out.println("释放锁后, counter 的状态: ");
counter.printObjectStruct();
}
}
偏向锁状态

偏向锁状态

轻量级锁状态

重量级锁状态

无锁状态


