目录
- 一、什么是悲观锁?
- 二、数据库悲观锁的实现
-
- [2.1 共享锁(Shared Lock / Read Lock)](#2.1 共享锁(Shared Lock / Read Lock))
- [2.2 排他锁(Exclusive Lock / Write Lock)](#2.2 排他锁(Exclusive Lock / Write Lock))
- [2.3 表级锁](#2.3 表级锁)
- [2.4 间隙锁(Gap Lock)和临键锁(Next-Key Lock)](#2.4 间隙锁(Gap Lock)和临键锁(Next-Key Lock))
- 三、Java中的悲观锁实现
-
- [1. synchronized关键字](#1. synchronized关键字)
- 2、ReentrantLock(可重入锁)
- 3、ReadWriteLock(读写锁)
一、什么是悲观锁?
"先加锁,后操作" - 假设事务之间一定会发生冲突,所以在操作数据之前先获取锁,确保同一时间只有一个事务能操作数据。
悲观锁总是假设最坏的情况 ,认为共享资源每次被访问的时候就会出现问题 (比如共享数据被修改),所以每次在获取资源操作的时候都会上锁 ,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
像 Java 中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

java
public void performSynchronisedTask() {
synchronized (this) {
// 需要同步的操作
}
}
private Lock lock = new ReentrantLock();
lock.lock();
try {
// 需要同步的操作
} finally {
lock.unlock();
}
高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题(线程获得锁的顺序不当时),影响代码的正常运行。
二、数据库悲观锁的实现
2.1 共享锁(Shared Lock / Read Lock)
sql
-- 共享锁:允许多个事务同时读取,但阻止任何事务写入
BEGIN;
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务可以加共享锁,但不能加排他锁
-- 事务2可以执行:
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE; -- ✅
-- 但不能执行:
SELECT * FROM products WHERE id = 1 FOR UPDATE; -- ❌ 会阻塞
-- 使用场景:读取数据,确保在事务期间数据不被修改
2.2 排他锁(Exclusive Lock / Write Lock)
sql
-- 排他锁:只允许一个事务读写,其他事务完全不能访问
BEGIN;
SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- 其他事务不能加任何锁:
SELECT * FROM products WHERE id = 1; -- ❌ 普通查询(有事务隔离级别影响)
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE; -- ❌
SELECT * FROM products WHERE id = 1 FOR UPDATE; -- ❌
-- 使用场景:更新数据,防止并发修改
2.3 表级锁
sql
-- 锁定整张表(谨慎使用!)
LOCK TABLES products WRITE; -- 写锁,独占整个表
-- 或
LOCK TABLES products READ; -- 读锁,允许其他读锁
-- 执行业务操作
SELECT * FROM products WHERE category = 'electronics';
UPDATE products SET price = price * 0.9 WHERE category = 'electronics';
-- 释放锁
UNLOCK TABLES;
-- 注意:MyISAM引擎默认使用表锁,InnoDB默认使用行锁
2.4 间隙锁(Gap Lock)和临键锁(Next-Key Lock)
sql
-- InnoDB特有的锁机制,防止幻读
BEGIN;
-- 对id > 100且<200的范围加间隙锁
SELECT * FROM products WHERE id > 100 AND id < 200 FOR UPDATE;
-- 其他事务不能在这个范围内插入新记录
INSERT INTO products (id, name) VALUES (150, 'New Product'); -- ❌ 会阻塞
三、Java中的悲观锁实现
1. synchronized关键字
sql
// 方法级别的悲观锁
public class InventoryService {
private Map<Long, Integer> stockMap = new HashMap<>();
// 1. 实例方法锁(锁住当前对象)
public synchronized boolean deductStock(Long productId, Integer quantity) {
Integer stock = stockMap.get(productId);
if (stock != null && stock >= quantity) {
stockMap.put(productId, stock - quantity);
return true;
}
return false;
}
// 2. 静态方法锁(锁住类对象)
public static synchronized void logOperation(String message) {
System.out.println(message);
}
// 3. 同步代码块(更细粒度控制)
private final Object lock = new Object();
public boolean deductStockWithBlock(Long productId, Integer quantity) {
// 只锁定关键代码,提高并发性
synchronized (lock) {
Integer stock = stockMap.get(productId);
if (stock != null && stock >= quantity) {
stockMap.put(productId, stock - quantity);
return true;
}
return false;
}
}
// 4. 双重检查锁定(Double-Checked Locking)
private volatile InventoryCache cache;
public InventoryCache getCache() {
if (cache == null) { // 第一次检查(不加锁)
synchronized (this) {
if (cache == null) { // 第二次检查(加锁)
cache = new InventoryCache();
}
}
}
return cache;
}
}
2、ReentrantLock(可重入锁)
sql
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class PessimisticLockWithReentrant {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Object[] items = new Object[100];
private int count, putIndex, takeIndex;
// 基本用法
public void basicUsage() {
lock.lock(); // 获取锁
try {
// 临界区代码
System.out.println("线程安全操作");
} finally {
lock.unlock(); // 必须释放锁
}
}
// 尝试获取锁(非阻塞)
public boolean tryLockExample() {
if (lock.tryLock()) { // 立即返回,不会阻塞
try {
// 获取锁成功
return true;
} finally {
lock.unlock();
}
} else {
// 获取锁失败
return false;
}
}
// 带超时的尝试获取锁
public boolean tryLockWithTimeout() throws InterruptedException {
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 最多等待1秒
try {
return true;
} finally {
lock.unlock();
}
}
return false;
}
// 可中断的锁
public void interruptibleLock() throws InterruptedException {
lock.lockInterruptibly(); // 可响应中断
try {
// 执行长时间操作
while (!Thread.currentThread().isInterrupted()) {
// 业务逻辑
}
} finally {
lock.unlock();
}
}
// 公平锁(按请求顺序获取锁)
private final ReentrantLock fairLock = new ReentrantLock(true); // true表示公平锁
// 条件变量(Condition)使用
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待"不满"条件
}
items[putIndex] = item;
if (++putIndex == items.length) {
putIndex = 0;
}
count++;
notEmpty.signal(); // 通知"不空"条件
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待"不空"条件
}
Object item = items[takeIndex];
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
notFull.signal(); // 通知"不满"条件
return item;
} finally {
lock.unlock();
}
}
}
3、ReadWriteLock(读写锁)
sql
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class InventoryCache {
private final Map<Long, Product> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 读操作(共享锁)
public Product getProduct(Long productId) {
readLock.lock(); // 获取读锁(可多个线程同时获取)
try {
return cache.get(productId);
} finally {
readLock.unlock();
}
}
// 写操作(排他锁)
public void updateProduct(Product product) {
writeLock.lock(); // 获取写锁(独占)
try {
cache.put(product.getId(), product);
} finally {
writeLock.unlock();
}
}
// 锁降级:写锁降级为读锁
public void lockDowngrade() {
writeLock.lock(); // 先获取写锁
try {
// 修改数据
cache.put(1L, new Product());
// 在释放写锁前获取读锁(锁降级)
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁,但还持有读锁
}
try {
// 仍然持有读锁,可以安全读取
Product p = cache.get(1L);
System.out.println(p);
} finally {
readLock.unlock();
}
}
// 锁升级:读锁升级为写锁(不支持,会导致死锁)
public void lockUpgradeInvalid() {
readLock.lock();
try {
// 这里不能直接获取写锁,会导致死锁
// writeLock.lock(); // ❌ 错误!
// 必须释放读锁后再获取写锁
} finally {
readLock.unlock();
}
writeLock.lock(); // ✅ 正确方式
try {
// 写操作
} finally {
writeLock.unlock();
}
}
}
