在现实场景中,抢票代码,如果不加锁,就会出现超卖或者一张票卖给多个人
Synchronized对象锁采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其它线程再想获取这个对象锁时就会阻塞住,代码如下
public class synchronizedTest {
// 创建一个静态对象作为锁
static Object lock = new Object();
// 初始票数
int ticketNum = 20;
// 获取票的方法,使用 synchronized 修饰确保线程安全
public synchronized void getTicket() {
// 使用当前对象作为锁
synchronized (this) {
// 如果票数已经为零,则返回
if (ticketNum <= 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "抢到一张票,剩余:" + ticketNum);
// 非原子性操作,扣除一张票
ticketNum--;
}
}
public static void main(String[] args) {
// 创建 synchronizedTest 实例
synchronizedTest synchronizedTest = new synchronizedTest();
// 创建并启动 20 个线程
for (int i = 0; i < 20; i++) {
// 调用获取票的方法
new Thread(() -> synchronizedTest.getTicket()).start();
}
}
}
通过以上代码,加synchronized锁,就可以防止超卖
特别说明:synchronized 关键字的底层实现涉及到 Java 虚拟机中的监视器(Monitor)机制。每个 Java 对象都与一个 Monitor 相关联,Monitor 负责对象的锁定和解锁,以及线程的阻塞和唤醒。
Monitor 被翻译为监视器,是由jvm提供,c++语言实现
使用一下简单代码中查看monitor,通过javap命令查看clsss的字节码
public class MonitorTest {
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
}
图片
思考:为什么会出现两个monitorexit
有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁
monitor主要就是跟这个对象产生关联,如下图
图片
Monitor内部具体的存储结构:
具体的流程:
面试官:synchronized关键字的底层原理?
- Synchronized【对象锁】
- 采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
- 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
- 在monitor内部有三个属性,分别是owner、entrylist、waitset
- 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程