Java最常用的三个锁:synchronized、AQS锁(以ReentrantLock为例)、CAS锁(以AtomicInteger为例)。
锁的分类
锁
- 悲观锁 总是害怕别人抢夺,实实在在地上锁来保持互斥
- synchronized
- AQS(AbstractQueuedSynchronizer,抽象队列同步器)
- ……
- 乐观锁 认为不会抢夺,不实际上上锁
- CAS(Compare and Swap)
- 版本号、时间戳等
面试中主要碰到的就是synchronized、AQS锁(以ReentrantLock为例)、CAS锁(以AtomicInteger为例)。
synchronized
这是一个关键字,可以加在方法上,也可以加在一段代码块上。它可以锁住一个对象,或者一个类(实质上是一个Class实例)。其底层原理在于对象头里的Mark Word里标记了有没有被上锁。
用法
private static int availableSeats = 2; // "数学课"的可用名额
//...
@Override
public void run() {
synchronized (CourseSelectionDemo3.class) {
if (availableSeats > 0) {
// 还有名额,学生成功选课
System.out.println(name + " 抢到了数学课的名额,剩余名额:" + (--availableSeats));
} else {
// 名额已满,学生无法选课
System.out.println(name + " 无法抢到数学课的名额,名额已满");
}
}
}
ReentrantLock
本质上是基于AQS的acquire、release。
ReentrantLock也用到了Condition。
@Override
public void run() {
try {
// 尝试获取选课的锁
lock.lock();
if (availableSeats > 0) {
// 还有名额,学生成功选课
System.out.println(name + " 抢到了数学课的名额,剩余名额:" + (--availableSeats));
} else {
// 名额已满,学生无法选课
System.out.println(name + " 无法抢到数学课的名额,名额已满");
}
} finally {
// 释放选课的锁
lock.unlock();
}
}
CAS的AtomicInteger
这样的整数自己有线程安全。CAS的机制是轮询,CPU开销大。它总是有一个预估值,然后取出实际值,如果预估值和实际值相符合,就写入。否则它会一直轮询,变成一个自旋锁。
CAS锁容易出现ABA问题,就是一个数据被别的线程修改了、又修改回去了。本线程无法得知它是否被修改过。
private static final AtomicInteger availableSeats = new AtomicInteger(2); // "数学课"的可用名额
//...
@Override
public void run() {
int remainingSeats = availableSeats.decrementAndGet();
if (remainingSeats >= 0) {
// 还有名额,学生成功选课
System.out.println(name + " 抢到了数学课的名额,剩余名额:" + remainingSeats);
} else {
// 名额已满,学生无法选课
System.out.println(name + " 无法抢到数学课的名额,名额已满");
}
}
对比
特性 | synchronized |
ReentrantLock |
CAS 锁 |
---|---|---|---|
底层原理 | 使用 JVM 内置的监视器对象 (monitor) | 使用 AbstractQueuedSynchronizer
|
使用硬件级别的原子操作 |
性能开销 | 相对较高,由于涉及到内核态操作 | 相对较高,因为涉及到多个线程的管理 | 相对较低,硬件级别的原子操作 |
可中断性 | 不支持 | 支持 | 不支持 |
可重入性 | 支持,同一线程可以多次获取同一个锁 | 支持,同一线程可以多次获取同一个锁 | 支持,同一线程可以多次获取同一个锁 |
公平性 | 可以是公平锁或非公平锁,取决于实现 | 可以是公平锁或非公平锁,通过构造器设置 | 不支持 |
条件等待 | .不支持 | 支持 | 不支持 |
适合场景 | 适用于简单的同步需求,不需要复杂的同步控制 | 适用于需要更多同步控制选项,如可中断、公平性、条件等待的场景 | 适用于 读远大于写的场景 |
使用难度 | 相对较低,语法简单 | 相对较高,需要显式地获取和释放锁 | 相对较高,需要考虑线程安全问题 |