前几天在某技术平台上看到别人提的关于 Synchronized 的一个用法问题,我觉得挺有意思的,这个问题其实也是我三年前面试某公司的时候遇到的一个真题,当时不知道面试官想要考什么,没有回答的特别好,后来研究了一下就记住了。
所以看到这个问题的时候觉得特别亲切,准备分享给大家一起看看:
首先为了方便你看文章的时候复现问题,我给你一份直接拿出来就能跑的代码,希望你有时间的话也把代码拿出来跑一下:
public class SynchronizedTest {
public static void main(String[] args) {
Thread why = new Thread(new TicketConsumer(10), "why");
Thread mx = new Thread(new TicketConsumer(10), "mx");
why.start();
mx.start();
}
}
class TicketConsumer implements Runnable {
private volatile static Integer ticket;
public TicketConsumer(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "开始抢第" + ticket + "张票,对象加锁之前:" + System.identityHashCode(ticket));
synchronized (ticket) {
System.out.println(Thread.currentThread().getName() + "抢到第" + ticket + "张票,成功锁到的对象:" + System.identityHashCode(ticket));
if (ticket > 0) {
try {
//模拟抢票延迟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票,票数减一");
} else {
return;
}
}
}
}
}
程序逻辑也很简单,是一个模拟抢票的过程,一共 10 张票,开启两个线程去抢票。
票是共享资源,且有两个线程来消费,所以为了保证线程安全,TicketConsumer 的逻辑里面用了 synchronized 关键字。
这是应该是大家在初学 synchronized 的时候都会写到的例子,期望的结果是 10 张票,两个人抢,每张票只有一个人能抢到。
但是实际运行结果是这样的,我只截取开始部分的日志:
截图里面有三个框起来的部分。
最上面的部分,就是两个人都在抢第 10 张票,从日志输出上看也完全没有任何毛病,最终只有一个人抢到了票,然后进入到第 9 张票的争夺过程。
但是下面被框起来的第 9 张票的争夺部分就有点让人懵逼了:
why抢到第9张票,成功锁到的对象:288246497
mx抢到第9张票,成功锁到的对象:288246497
为什么两个人都抢到了第 9 张票,且成功锁到的对象都是一样的?
这玩意,超出认知了啊。
这两个线程怎么可能拿到同一把锁,然后去执行业务逻辑呢?
所以,提问者的问题就浮现出来了。
-
1.为什么 synchronized 没有生效?
-
2.为什么锁对象 System.identityHashCode 的输出是一样的?
为什么没有生效?
我们先来看一个问题。
首先,我们从日志的输出上已经非常明确地知道,synchronized 在第二轮抢第 9 张票的时候失效了。
经过理论知识支撑,我们知道 synchronized 失效,肯定是锁出问题了。
如果只有一把锁,多个线程来竞争同一把锁,synchronized 绝对是不会有任何毛病的。
但是这两个线程并没有达成互斥的条件,也就是说这里绝对存在的不止一把锁。
这是我们可以通过理论知识推导出来的结论。