youyichannel

志于道,据于德,依于仁,游于艺!

0%

Synchronized全解读-04

Synchronized锁升级 —— 偏向锁的撤销(非必需)

示例代码1:

启动前配置JVM参数:-XX:BiasedLockingStartupDelay=0

public class BiasedLockRelease1 {
static Thread A;
static Thread B;

public static void main(String[] args) {
final List<Object> list = new ArrayList<>();
A = new Thread(() -> {
Object obj = new Object();
list.add(obj);
System.out.println("A加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("A加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println("A加锁后:" + ClassLayout.parseInstance(obj).toPrintable());
// 防止竞争,执行完后唤醒B, A线程Terminated之后唤醒B线程
LockSupport.unpark(B);
}, "Thread - A");

B = new Thread(() -> {
LockSupport.park();
Object obj = list.get(0);
System.out.println("B加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("B加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
}
System.out.println("B加锁后:" + ClassLayout.parseInstance(obj).toPrintable());
System.out.println("新产生的对象:" + ClassLayout.parseInstance(new Object()).toPrintable());
}, "Thread - B");

A.start();
B.start();
}
}

运行结果:

锁只能升级,不能降级

1)A线程获取偏向锁,并且A线程Terminated退出。B线程争抢偏向锁,会直接升级当前对象的锁为轻量级锁。(仅针对争抢了一次的情况)

示例代码2:

public class BiasedLockRelease2 {
static Thread A;
static Thread B;

public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
System.out.println("A加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
A = new Thread(() -> {
synchronized (obj) {
System.out.println("A加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "Thread - A");
A.start();

Thread.sleep(500);

System.out.println("B加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
B = new Thread(() -> {
synchronized (obj) {
System.out.println("B加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "Thread - B");
B.start();
}
}

2)A线程获取偏向锁,并且A线程尚未释放偏向锁(还处于同步代码块中),B线程此时来争抢偏向锁,此时会直接升级为重量级锁。

修改BiasedLockRelease2的代码:

A = new Thread(() -> {
synchronized (obj) {
System.out.println("A加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "Thread - A");
A.start();

3)A线程获取偏向锁,并且A线程释放了偏向锁,但是A线程还没有Terminated,此时B线程来争抢锁,此时会升级为轻量级锁。

=> 总结:(针对争抢一次的情况)

当尝试第一次争抢偏向锁时,如果A线程已经Terminated,升级为轻量级锁;如果A线程尚未Terminated,并且未释放锁,直接升级为重量级锁;如果A线程尚未Terminated,并且已经释放了锁,升级为轻量级锁。

示例代码3:

public class BiasedLockRelease3 {
public static void main(String[] args) throws InterruptedException {
List<Object> list = new ArrayList<>();
// 创建20个线程争抢锁
for (int i = 0; i < 20; i++) {
Object obj = new Object();
list.add(obj);
System.out.println("A加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
Thread A = new Thread(() -> {
synchronized (obj) {
System.out.println("A加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
A.start();
}
Thread.sleep(200);

for (int i = 0; i < 20; i++) {
Object obj = list.get(i);
System.out.println("B加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
Thread B = new Thread(() -> {
synchronized (obj) {
System.out.println("B加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
B.start();
}

Thread.sleep(5000);
synchronized (list.get(19)) {
System.out.println("再次加锁:" + ClassLayout.parseInstance(list.get(19)).toPrintable());
}
System.out.println("新产生的对象:" + ClassLayout.parseInstance(new Object()).toPrintable());
}
}

4)A线程获取偏向锁,并且A线程没有释放偏向锁(处于同步代码块中),B线程多次争抢锁,会在加锁过程中采用重量级锁;但是锁被释放之后,当前对象还是会以轻量级锁的初始状态执行。

锁升级是在线程运行过程中争抢过程中的一种升级。

5)A线程获取偏向锁,并且A线程释放了偏向锁,但是A线程还没有Terminated,此时B线程来多次争抢锁,部分争抢会升级为轻量级锁,部分争抢会保持偏向锁。

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点没有正在执行的安全码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着;如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁线程的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向其他线程,要么恢复到无锁状态或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。