当两个线程使用synchronized同步两个类型和值都相同的final变量时,神奇的一幕发生了,这两个锁变成了同一把锁!子线程先获取到锁0后,主线程获取锁1阻塞了,直到子线程释放锁0主线程才获取到锁1!两个线程“被”同步了!
private final Boolean lock0 = true;
private final Boolean lock1 = true;
private void syncTest() throws InterruptedException {
new Thread(() -> {
try {
log.info("child try lock 0");
synchronized (lock0) {
log.info("child lock 0");
Thread.sleep(1000);
}
log.info("child unlock 0");
} catch (Exception e) {
log.error("", e);
}
}).start();
Thread.sleep(100);
log.info("main try lock 1");
synchronized (lock1) {
log.info("main lock 1");
}
log.info("main unlock 1");
}
以上代码输出为
2022-04-11 16:36:08.998 INFO 4924 --- [ Thread-2] child try lock 0
2022-04-11 16:36:08.998 INFO 4924 --- [ Thread-2] child lock 0
2022-04-11 16:36:09.097 INFO 4924 --- [ main] main try lock 1
2022-04-11 16:36:09.998 INFO 4924 --- [ Thread-2] child unlock 0
2022-04-11 16:36:09.998 INFO 4924 --- [ main] main lock 1
2022-04-11 16:36:09.998 INFO 4924 --- [ main] main unlock 1
若把lock1改为false,则正常,输出为
2022-04-11 16:41:51.330 INFO 22512 --- [ Thread-2] child try lock 0
2022-04-11 16:41:51.330 INFO 22512 --- [ Thread-2] child lock 0
2022-04-11 16:41:51.430 INFO 22512 --- [ main] main try lock 1
2022-04-11 16:41:51.430 INFO 22512 --- [ main] main lock 1
2022-04-11 16:41:51.430 INFO 22512 --- [ main] main unlock 1
2022-04-11 16:41:52.330 INFO 22512 --- [ Thread-2] child unlock 0
我猜测jvm把两个类型和值都相同的final变量优化为同一个对象,这样使用synchronized同步这两个变量时,实际为同步同一个变量,导致两个线程同步。打印log.info("{}", lock0 == lock1);输出为true,证明猜测。
若把两个锁变量换成Object类型时,编译器不会优化为同一个对象,我猜测编译器的这个优化只针对基本类型。所以使用synchronized同步变量时,尽量别使用基本类型变量。