常见锁的应用场景对比
| 锁类型 | 特点 | 适用场景 | 实现方式 |
|---|---|---|---|
| 乐观锁 | 高性能,但需处理冲突检测 | 读多写少,低冲突 | 使用版本号或 CAS |
| 悲观锁 | 数据安全,但性能较低 | 写多读少,高冲突 | 使用数据库的 for update 或 synchronized 实现 |
| 自旋锁 | 减少上下文切换开销 | 锁竞争少,持锁时间短 | 使用 Atomicxxx包下面的类 和循环 |
| 读写锁 | 提高读性能 | 读多写少,允许并发读取 | 使用 ReentrantReadWriteLock 实现 |
| 可重入锁 | 线程可以重入锁 | 嵌套锁场景 | 使用 ReentrantLock 或 synchronized
|
| 偏向锁 | 偏向单线程场景,性能极高 | 无竞争的单线程场景 | JVM 自动优化(synchronized 初始状态) |
| 轻量级锁 | 竞争少,通过自旋优化 | 短期锁竞争 | JVM 自动优化(synchronized 竞争升级) |
| 重量级锁 | 阻塞线程,性能较低 | 高竞争,锁时间较长 | JVM 自动优化(synchronized 高竞争升级) |
| 分段锁 | 细化锁粒度,提升并发性 | 数据分区操作 | jdk1.7之前的ConcurrentHashMap 的分段机制 |
| 统一锁 | 支持乐观读、悲观写结合 | 复杂并发场景 | 使用 StampedLock 实现 |
1. 乐观锁与悲观锁
乐观锁
- 特点:假设并发冲突较少,每次操作前不加锁,操作后通过版本号或 CAS(Compare And Swap)检测冲突。
-
实现方式:
CAS。 - 优点:无需线程阻塞,性能较高。
- 适用场景:读多写少的场景。
悲观锁
- 特点:假设并发冲突较多,每次操作前加锁,保证线程互斥。
-
实现方式:
synchronized或ReentrantLock。 - 优点:数据安全。
- 适用场景:写多读少的场景。
2. 自旋锁
- 特点:线程通过自旋(不断尝试)等待锁,而非直接进入阻塞状态。
-
实现方式:依赖
CAS操作。 - 优点:避免线程上下文切换的开销。
- 缺点:锁竞争激烈时会浪费 CPU 资源。
3. CAS(Compare And Swap)
- 特点:一种无锁并发机制,通过比较内存中的值是否为期望值来更新数据。
-
实现类:
AtomicInteger,AtomicLong,ConcurrentHashMap等。 - 优点:高效。
- 缺点:可能引发 ABA 问题,需要配合版本号解决。
4. 排他锁
- 特点:一次只允许一个线程获取锁,其他线程阻塞。
-
实现方式:
synchronized,ReentrantLock。 - 应用场景:需要严格同步的场景。
5. 读写锁
- 特点:分为读锁和写锁,允许多个线程同时读,但写操作互斥。
-
实现类:
ReentrantReadWriteLock。 - 优点:提高读多写少场景的性能。
6. 可重入锁
- 特点:允许同一线程多次获取同一把锁,而不会导致死锁。
-
实现类:
ReentrantLock,synchronized。 - 优势:避免嵌套锁时死锁的问题。
7. 分段锁
- 特点:将锁分段管理,每段锁控制不同数据的访问。
-
实现类:
ConcurrentHashMap(JDK 7 使用)。 - 优点:减少锁粒度,提高并发性能。
8. 偏向锁、轻量级锁、重量级锁(synchronized 内部优化)
偏向锁
- 特点:当锁只被一个线程持有时,记录线程 ID,避免加锁操作。
- 优点:性能极高。
- 限制:当锁被其他线程竞争时,升级为轻量级锁。
轻量级锁
- 特点:通过 CAS 自旋尝试获取锁。
- 优点:减少线程阻塞开销。
- 限制:当自旋超过阈值时,升级为重量级锁。
重量级锁
- 特点:线程阻塞等待锁释放,使用 OS 同步机制。
- 缺点:性能较低,线程上下文切换开销大。
9. 统一锁(StampedLock)
-
特点:
StampedLock提供乐观读锁和悲观写锁的组合。 -
实现类:
StampedLock。 -
优点:读多写少场景性能优于
ReentrantReadWriteLock。
10. 线程局部锁
- 特点:将锁粒度细化到每个线程独享的资源上,减少竞争。
-
实现方式:
ThreadLocal或每个线程独占锁。
简单实现
// 1. 乐观锁实现(版本号机制)
class OptimisticLockExample {
private int version = 0;
public synchronized boolean updateData(int expectedVersion) {
if (this.version == expectedVersion) {
this.version++;
return true; // 更新成功
}
return false; // 更新失败
}
}
// 2. 自旋锁实现
class SpinLock {
private AtomicBoolean lock = new AtomicBoolean(false);
public void lock() {
while (!lock.compareAndSet(false, true)) {
// 自旋
}
}
public void unlock() {
lock.set(false);
}
}
// 3. CAS 实现
class CASExample {
private AtomicInteger value = new AtomicInteger(0);
public int increment() {
int current;
do {
current = value.get();
} while (!value.compareAndSet(current, current + 1));
return current + 1;
}
}
// 4. 排他锁(synchronized 示例)
class ExclusiveLock {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
// 5. 读写锁实现
class ReadWriteLockExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private int value = 0;
public void write(int newValue) {
rwLock.writeLock().lock();
try {
value = newValue;
} finally {
rwLock.writeLock().unlock();
}
}
public int read() {
rwLock.readLock().lock();
try {
return value;
} finally {
rwLock.readLock().unlock();
}
}
}
// 6. 可重入锁实现
class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock();
try {
// Critical section
} finally {
lock.unlock();
}
}
}
// 7. 分段锁实现(ConcurrentHashMap 示例)
class SegmentLockExample {
private final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
public void put(int key, String value) {
map.put(key, value);
}
public String get(int key) {
return map.get(key);
}
}
// 8. 偏向锁、轻量级锁、重量级锁(synchronized 自动优化)
class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
}
// 9. StampedLock 示例
class StampedLockExample {
private final StampedLock lock = new StampedLock();
private int value = 0;
public void write(int newValue) {
long stamp = lock.writeLock();
try {
value = newValue;
} finally {
lock.unlockWrite(stamp);
}
}
public int optimisticRead() {
long stamp = lock.tryOptimisticRead();
int currentValue = value;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentValue = value;
} finally {
lock.unlockRead(stamp);
}
}
return currentValue;
}
}
// 10. ThreadLocal 实现线程局部锁
class ThreadLocalLockExample {
private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public void set(int value) {
threadLocal.set(value);
}
public int get() {
return threadLocal.get();
}
}
日常开发使用Redisson的最佳实现
1.加锁时使用tryLock()方法
trylock()方法可以设置等待时长,释放锁的时间可以避免因为直接使用lock()线程一直等待导致死锁问题。
2.在catch到线程中断时,使用Thread.currentThread().interrupt()恢复中断状态
Thread.currentThread().interrupt()是一种推荐的安全实践,确保线程中断信号能够正确传播,不会因为 InterruptedException 的捕获而被意外丢失。
- 如果继续执行逻辑(如抛出自定义异常),需要调用 Thread.currentThread().interrupt() 来恢复中断状态。
- 如果直接返回或终止线程,则不需要特别处理中断标志。
3.释放锁时判断是否持有锁isHeldByCurrentThread()
这是 ReentrantLock 提供的一个方法,用于判断当前线程是否持有该锁。这可以防止在以下场景中错误地调用 unlock():
- 如果当前线程未持有锁而调用 unlock(),会抛出 IllegalMonitorStateException 异常。
- 通过此检查,保证只有当前持有锁的线程才会尝试释放锁。
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 订单表 服务实现类
*
* @author wds
* @since 2024-11-27
*/
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
/**
* 下单
*
* @param reqDto 下单参数
* @return Void
*/
@Override
@GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public R<Void> add(OrderAddReq reqDto) {
String hash = MD5Utils.encrypt2HexString(JacksonUtils.toJson(reqDto));
RLock lock = redisson.getLock(String.format(OrderRedisKeyConstants.ORDER_ADD_HASH, hash));
try {
boolean isLock = lock.tryLock(1000, 15000, TimeUnit.MILLISECONDS);
if (!isLock) {
throw new BusinessException(OrderBusinessCodeEnum.ORDER_ADD_TO_FREQUENT);
}
OrderDetailCreateDto orderDetailCreateDto = valOrder(reqDto);
Order order = reqDto.buildOrder(orderDetailCreateDto);
log.info("订单:{}", JacksonUtils.toJson(order));
AccountChangeReq accountChangeReq = RemoteAccountConvert.convert2OrderPay(order);
R<Void> remoteTransferResult = accountFeign.payOrder(accountChangeReq, SecurityConstants.FROM_IN);
if (!remoteTransferResult.isSuccess()) {
log.error("调用账户服务支付失败,CoinExchangeReqDTO:{},remoteTransferResult:{}", JacksonUtils.toJson(accountChangeReq), remoteTransferResult);
throw new BusinessException(ExtraCodeEnum.DATA_DEAL_ERROR.code, remoteTransferResult.getMsg());
}
if (!save(order.pay())) {
log.error("订单插入数据库失败,order:{}", JacksonUtils.toJson(order));
throw new BusinessException(ExtraCodeEnum.SQL_ERROR.code, "order.order_add_failed");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("下单异常,reqDto:{}", JacksonUtils.toJson(reqDto), e);
throw new BusinessException(ExtraCodeEnum.DATA_DEAL_ERROR.code, "guess.order.order_add_failed");
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return R.ok();
}
}