悲观锁讲解

目录

一、什么是悲观锁?

"先加锁,后操作" - 假设事务之间一定会发生冲突,所以在操作数据之前先获取锁,确保同一时间只有一个事务能操作数据。
悲观锁总是假设最坏的情况 ,认为共享资源每次被访问的时候就会出现问题 (比如共享数据被修改),所以每次在获取资源操作的时候都会上锁 ,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

JavasynchronizedReentrantLock等独占锁就是悲观锁思想的实现。

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();
        }
    }
}
相关推荐
小杰帅气2 小时前
智能指针喵喵喵
开发语言·c++·算法
老华带你飞2 小时前
个人网盘管理|基于springboot + vue个人网盘管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
soft20015252 小时前
MySQL Buffer Pool深度解析:冷热数据分离下的LRU链表工作机制
数据库·mysql·链表
whn19772 小时前
磁盘空间不足导致oracle的system01.dbf损坏
数据库·oracle
hudawei9962 小时前
对比kotlin和flutter中的异步编程
开发语言·flutter·kotlin·异步·
南棱笑笑生2 小时前
20251219给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-5.10】后解决启动不了报atf-2的问题
linux·c语言·开发语言·rockchip
此生只爱蛋2 小时前
【Redis】Hash 哈希
数据库·redis·哈希算法
deephub2 小时前
ONNX Runtime Python 推理性能优化:8 个低延迟工程实践
开发语言·人工智能·python·神经网络·性能优化·onnx
蕨蕨学AI2 小时前
【Wolfram语言】22 机器学习
开发语言·wolfram