synchronized的实现原理是啥?
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | 夜勿语
来源 | urlify.cn/MvYZZj
前言
上一篇分析了优化后的synchronized在不同场景下对象头中的表现形式,还记得那个结论吗?当一个线程第一次获取锁后再去拿锁就是偏向锁,如果有别的线程和当前线程交替执行就膨胀为轻量级锁,如果发生竞争就会膨胀为重量级锁。这句话看起来很简单,但实际上synhronized的膨胀过程是非常复杂的,有许多场景和细节需要考虑,本篇就对其进行详细分析。
正文
先来看一个案例代码:
public class TestInflate {
static Thread t2;
static Thread t3;
static Thread t1;
static int loopFlag = 19;
public static void main(String[] args) throws InterruptedException {
//a 没有线程偏向---匿名 101偏向锁
List list = new ArrayList<>();
t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < loopFlag; i++) {
A a = new A();
list.add(a);
synchronized (a) {
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintableTest(a));
}
}
log.debug("========t2=================");
LockSupport.unpark(t2);
}
};
t2 = new Thread() {
@Override
public void run() {
LockSupport.park();
for (int i = 0; i < loopFlag; i++) {
A a = list.get(i);
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
synchronized (a) {
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
}
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
}
log.debug("======t3=====================================");
LockSupport.unpark(t3);
}
};
t3 = new Thread() {
@Override
public void run() {
LockSupport.park();
for (int i = 0; i < loopFlag; i++) {
A a = list.get(i);
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
synchronized (a) {
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
}
log.debug(i + " " + ClassLayout.parseInstance(a).toPrintable(a));
}
}
};
t1.start();
t2.start();
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new A()).toPrintable());
}
偏向锁
偏向锁没什么好演示的,但是在源码中获取偏向锁是第一步,且逻辑比较多,有以下几点需要注意:
轻量锁
首先注释掉t3,先设置loopFlag=19运行t1和t2,你能猜到打印的对象头是什么样的么?(为节省篇幅,下文对象头都只截取最后8位展示)
15:57:38.579 [Thread-0] DEBUG cn.dark.ex6.TestInflate - 0 00000101
15:57:38.580 [Thread-0] DEBUG cn.dark.ex6.TestInflate - 1 00000101
......
15:57:38.582 [Thread-0] DEBUG cn.dark.ex6.TestInflate - 17 00000101
15:57:38.582 [Thread-0] DEBUG cn.dark.ex6.TestInflate - 18 00000101
15:57:38.582 [Thread-0] DEBUG cn.dark.ex6.TestInflate - ========t2=================
15:57:38.582 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 00000101
15:57:38.583 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 10000000
15:57:38.583 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 00000001
15:57:38.583 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 1 00000101
15:57:38.583 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 1 10000000
15:57:38.583 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 1 00000001
......
15:57:38.589 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 17 00000101
15:57:38.589 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 17 10000000
15:57:38.589 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 17 00000001
15:57:38.589 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 00000101
15:57:38.590 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 10000000
15:57:38.590 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 00000001
15:57:38.590 [Thread-1] DEBUG cn.dark.ex6.TestInflate - ======t3=====================================
15:57:38.590 [main] DEBUG cn.dark.ex6.TestInflate - cn.dark.entity.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 2c 6a 01 f8 (00101100 01101010 00000001 11111000) (-134125012)
12 4 (loss due to the next object alignment)
批量重偏向
16:52:02.005 [Thread-0] DEBUG cn.dark.ex6.TestInflate - ========t2=================
16:52:02.005 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 00000101
16:52:02.005 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 00110000
16:52:02.005 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 0 00000001
......
16:52:02.011 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 00110000
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 18 00000001
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 19 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 19 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 19 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 20 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 20 00000101
16:52:02.012 [Thread-1] DEBUG cn.dark.ex6.TestInflate - 20 00000101
16:54:45.035 [main] DEBUG cn.dark.ex6.TestInflate - cn.dark.entity.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 01 00 00 (00000101 00000001 00000000 00000000) (261)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 2c 6a 01 f8 (00101100 01101010 00000001 11111000) (-134125012)
12 4 (loss due to the next object alignment)
批量撤销
17:15:46.640 [Thread-1] DEBUG cn.dark.ex6.TestInflate - ======t3=====================================
17:15:46.640 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 0 00000001
17:15:46.640 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 0 11100000
17:15:46.640 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 0 00000001
......
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 18 00000001
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 18 11100000
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 18 00000001
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 19 00000101
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 19 11100000
17:15:46.644 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 19 00000001
.......
17:15:46.650 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 39 00000101
17:15:46.650 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 39 11100000
17:15:46.651 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 39 00000001
......
17:15:46.652 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 49 00000101
17:15:46.652 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 49 11100000
17:15:46.653 [Thread-2] DEBUG cn.dark.ex6.TestInflate - 49 00000001
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 2c 6a 01 f8 (00101100 01101010 00000001 11111000) (-134125012)
12 4 (loss due to the next object alignment)
// 当前时间
jlong cur_time = os::javaTimeMillis();
// 该类上一次批量撤销的时间
jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
// 该类偏向锁撤销的次数
int revocation_count = k->biased_lock_revocation_count();
// BiasedLockingBulkRebiasThreshold是重偏向阈值(默认20),
// BiasedLockingBulkRevokeThreshold是批量撤销阈值(默认40),
// BiasedLockingDecayTime默认25000。
if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
(revocation_count < BiasedLockingBulkRevokeThreshold) &&
(last_bulk_revocation_time != 0) &&
(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
// 重置计数器
k->set_biased_lock_revocation_count(0);
revocation_count = 0;
}
重量锁
public class Demo2 {
private static Demo2 lock = new Demo2();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
synchronized (lock) {
log.info(Thread.currentThread().getName());
}
});
}
synchronized (lock) {
for (Thread thread : threads) {
thread.start();
// 睡眠一下保证线程的启动顺序
Thread.sleep(100);
}
}
}
}
上面程序创建了10个线程,然后主线程拿到锁后依次启动10个线程,这10个线程内又会分别去获取锁,因为被主线程占有,就会膨胀为重量锁进入阻塞,最终打印结果如下:
16:25:49.877 [Thread-9] INFO cn.dark.mydemo.sync.Demo2 - Thread-9
16:25:49.879 [Thread-8] INFO cn.dark.mydemo.sync.Demo2 - Thread-8
16:25:49.879 [Thread-7] INFO cn.dark.mydemo.sync.Demo2 - Thread-7
16:25:49.879 [Thread-6] INFO cn.dark.mydemo.sync.Demo2 - Thread-6
16:25:49.879 [Thread-5] INFO cn.dark.mydemo.sync.Demo2 - Thread-5
16:25:49.879 [Thread-4] INFO cn.dark.mydemo.sync.Demo2 - Thread-4
16:25:49.879 [Thread-3] INFO cn.dark.mydemo.sync.Demo2 - Thread-3
16:25:49.879 [Thread-2] INFO cn.dark.mydemo.sync.Demo2 - Thread-2
16:25:49.879 [Thread-1] INFO cn.dark.mydemo.sync.Demo2 - Thread-1
16:25:49.879 [Thread-0] INFO cn.dark.mydemo.sync.Demo2 - Thread-0
可以看到10个线程并不是按照启动顺序执行的,而是以相反的顺序被唤醒并执行。
以上就是Synchronized的膨胀过程以及底层的一些实现原理,最后我画了一张synchronized锁膨胀过程的图帮助理解,有不对的地方欢迎指出:
总结
感谢点赞支持下哈
评论