背景
最近再看关于锁升级的内容,一方面这个是编写代码时程序性能提高的一个利器,另一方面这部分也会是面试时候的热门话题。那么作者最开始也是通过b站视频包括一些csdn上面的资料去看,最终发现只是有一些结论,而没有具体的例子。这篇文章里一切都是以jdk8为环境基础展开。
一种观点是:锁在没有竞争的情况下会是偏向锁,在遇到竞争的时候会升级成轻量级锁,再遇到竞争了这个锁会进行一定次数的自旋,当次数计满了就会升级成为重量级锁。
另一种观点是:无论是轻量级锁还是偏向锁在遇到竞争的时候都会升级成重量级锁,而重量级锁会通过自旋的操作来避免阻塞的发生来提高性能,并且自旋也是自适应的。
值得一提的是,在jdk8中,偏向锁的开启是有延迟的,并不是一开启的时候就是偏向锁,而自旋锁是一开始就有的。
下面我们就围绕着代码和实际情况来看偏向锁,轻量级锁,重量级锁,以及偏向锁的重偏向和撤销的过程。
代码
这里先把测试的代码给大家,根据满叔的资料进行改编。大家在观察代码的时候要注意两个线程是否发生了竞争,包括这个日志打印的顺序是怎么样的。
package com.zzm.juc.syn;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
import java.util.Vector;
import java.util.concurrent.locks.LockSupport;
/**
* @BelongsProject: happystudy
* @BelongsPackage: com.zzm.juc.syn
* @Author: zzm
* @CreateTime: 2024-03-27 11:23
* @Description: TODO
* @Version: 1.0
*/
@Slf4j(topic = "c.SyncWaitNotify")
public class SyncWaitNotify {
private int flag;
private int loopNumber;
static Thread t1,t2,t3;
private final Object lock = new Object();
public SyncWaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
public void print(int waitFlag, int nextFlag, String str) {
log.debug("对象头" + ClassLayout.parseInstance(lock).toPrintable());
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while (this.flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
log.debug("对象头" + ClassLayout.parseInstance(lock).toPrintable());
this.notifyAll();
}
}
}
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}, "t3");
t3.start();
t3.join();
Dog dd = new Dog();
log.debug(ClassLayout.parseInstance(dd).toPrintable());
for(int i=0;i<2;i++){
synchronized (dd) {
log.debug("不可偏向"+ClassLayout.parseInstance(dd).toPrintable());
}
}
log.debug(ClassLayout.parseInstance(dd).toPrintable());
}
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
}
}, "t2");
t2.start();
}
private static void test2() throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintable());
}
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintable());
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintable());
}
log.debug(ClassLayout.parseInstance(d).toPrintable());
}, "t2");
t2.start();
}
public static void main(String[] args) throws InterruptedException {
// SyncWaitNotify syncWaitNotify = new SyncWaitNotify(1, 2);
// new Thread(() -> {
// syncWaitNotify.print(1, 2, "a");
// }).start();
// new Thread(() -> {
// syncWaitNotify.print(2, 3, "b");
// }).start();
// new Thread(() -> {
// syncWaitNotify.print(3, 1, "c");
// }).start();
test4();
// test3();
// test2();
}
}
markword阅读:
这里的toPrintable读出来的字符串,前两行是markword,并且要倒着读也就是从后往前读,这个详细了解可以去搜索一下这个classlayout的相关博客是如何查看对象头信息的。
锁升级
比如说这段代码
t1,t2没有竞争,打印出来头信息最后两位为0
那么全程这个就是轻量级锁,因为偏向锁是有延迟的,所以我们加一个参数
这个对象加锁后就会变成偏向锁
带偏向锁的情况下 发生了竞争
可以看到一开始是偏向锁
最后变成了10,直接成为了重量级锁跳过了轻量级。而经过测试轻量级锁发生竞争也会立刻变成重量级锁。
test3->重偏向
一个对象,关闭偏向锁延迟,上来他是偏向锁,这个时候两个线程不会竞争但是会交替运行,那么他会由偏向锁,变成轻量级锁,现在准备30个对象,把他放到list里面,还是两个线程没有竞争的情况,t1线程对list里面对象加锁,都会变成偏向锁,在加锁的时候。
这个时候t1执行完毕到t2了,t2又把这list里面的30个对象依次取出来,进行加锁,偏向锁的重偏向是有阈值的,这里为20。那么在t2线程里面拿出的前20个对象,他的轻量级锁的线程id都是t1,他们都会从偏向锁变成轻量级锁。
但是超过20次了,
第21个jvm可能就会认为这个对象对于t1线程是不是偏向错了,因此他把后面还没来得及遍历的10个对象,一起把偏向锁的线程id变成了t2线程。
test4->批量撤销
但是同理,假设这个时候对象变成了40个,t1对每个对象加锁完之后,t2右对他们加锁,t2加锁完之后,t3又对他们进行加锁的重偏向,
当阈值达到40的时候,jvm可能就会认为这个类下的对象竞争比较激烈就不允许给这个类下的对象设置偏向锁了,再创建一个,加锁的时候也就变成了轻量级锁。