详解悲观锁

Java 中悲观锁的概念与实现方式

1. 悲观锁的概念

悲观锁(Pessimistic Locking)是一种并发控制机制,它假设在并发环境中,数据冲突是不可避免的,因此在操作共享资源时,总是先加锁,确保其他线程在锁释放前无法访问或修改该资源。悲观锁适合在高并发且数据冲突频繁的场景中使用。

2. 悲观锁的实现方式

2.1 使用 synchronized 关键字

synchronized 是 Java 中最常用的悲观锁实现方式,用于对代码块或方法进行同步,确保同一时间只有一个线程可以执行被锁定的代码。

实现步骤
  1. 同步代码块

    java复制

    public class PessimisticLockExample {
        private Object lock = new Object();
    
        public void updateData() {
            synchronized (lock) {
                // 被锁定的代码块
                System.out.println("Updating data...");
            }
        }
    }
    
  2. 同步方法

    java复制

    public class PessimisticLockExample {
        public synchronized void updateData() {
            // 整个方法被锁定
            System.out.println("Updating data...");
        }
    }
    
优点
  • 简单易用:Java 内置支持,代码简洁。
  • 自动释放锁:异常或方法结束时会自动释放锁。
缺点
  • 性能问题:锁的粒度较大时,可能导致性能瓶颈。
  • 死锁风险:不当使用可能导致死锁。

2.2 使用 ReentrantLock

ReentrantLock 是 Java 提供的显式锁,相较于 synchronized,它提供了更多的功能,如尝试锁、非阻塞锁、公平锁等。

实现步骤
  1. 创建锁对象

    java复制

    import java.util.concurrent.locks.ReentrantLock;
    
    public class PessimisticLockExample {
        private ReentrantLock lock = new ReentrantLock();
    
        public void updateData() {
            lock.lock(); // 获取锁
            try {
                // 被锁定的代码块
                System.out.println("Updating data...");
            } finally {
                lock.unlock(); // 确保释放锁
            }
        }
    }
    
  2. 尝试获取锁

    java复制

    if (lock.tryLock()) {
        try {
            // 被锁定的代码块
            System.out.println("Updating data...");
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("Failed to acquire lock");
    }
    
优点
  • 灵活性高:支持可中断锁、尝试锁、公平锁等。
  • 功能丰富:可以更精细地控制锁的行为。
缺点
  • 手动释放锁:需要显式调用 unlock(),否则可能导致死锁。
  • 代码复杂性:相较于 synchronized,实现稍显复杂。

2.3 使用数据库的悲观锁机制

在数据库层面,可以通过 SELECT ... FOR UPDATESELECT ... FOR SHARE 语句实现悲观锁,确保数据在事务提交前不会被其他事务修改。

实现步骤
  1. 使用 SELECT ... FOR UPDATE

    sql复制

    -- 查询数据并加锁
    SELECT * FROM tb_voucher_order WHERE voucher_id = 1 FOR UPDATE;
    
  2. 更新数据

    sql复制

    -- 更新数据
    UPDATE tb_voucher_order SET stock = stock - 1 WHERE voucher_id = 1;
    
  3. Java 实现

    java复制

    @Transactional
    public void updateVoucherOrder(Long voucherId) {
        // 使用 FOR UPDATE 查询
        SeckillVoucher voucher = seckillVoucherService.getForUpdateById(voucherId);
    
        // 扣减库存
        voucher.setStock(voucher.getStock() - 1);
        seckillVoucherService.save(voucher);
    
        // 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setUserId(UserHolder.getUser().getId());
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.save();
    }
    
优点
  • 强一致性:确保数据在事务提交前不会被其他事务修改。
  • 简单易用:直接利用数据库的锁机制。
缺点
  • 性能问题:在高并发场景下,可能导致数据库锁等待时间过长。
  • 死锁风险:不当使用可能导致死锁。

2.4 使用 Lock 接口的其他实现类

除了 ReentrantLock,Java 还提供了其他锁实现类,如 ReadWriteLockStampedLock

2.4.1 使用 ReadWriteLock

ReadWriteLock 支持读写分离,允许多个读操作同时进行,但写操作时会独占锁。

实现步骤
  1. 创建锁对象

    java复制

    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class PessimisticLockExample {
        private ReadWriteLock lock = new ReentrantReadWriteLock();
    
        public void updateData() {
            lock.writeLock().lock(); // 获取写锁
            try {
                System.out.println("Updating data...");
            } finally {
                lock.writeLock().unlock();
            }
        }
    
        public void readData() {
            lock.readLock().lock(); // 获取读锁
            try {
                System.out.println("Reading data...");
            } finally {
                lock.readLock().unlock();
            }
        }
    }
    
优点
  • 读写分离:允许多个读操作同时进行,提高并发性能。
  • 适合读多写少的场景:如查询多、更新少的业务。
缺点
  • 复杂性:实现和使用相对复杂。

2.4.2 使用 StampedLock

StampedLock 是 Java 8 引入的一种乐观锁和悲观锁结合的锁实现,支持读、写和乐观读模式。

实现步骤
  1. 创建锁对象

    java复制

    import java.util.concurrent.locks.StampedLock;
    
    public class PessimisticLockExample {
        private StampedLock lock = new StampedLock();
    
        public void updateData() {
            long stamp = lock.writeLock(); // 获取写锁
            try {
                System.out.println("Updating data...");
            } finally {
                lock.unlockWrite(stamp);
            }
        }
    
        public void readData() {
            long stamp = lock.readLock(); // 获取读锁
            try {
                System.out.println("Reading data...");
            } finally {
                lock.unlockRead(stamp);
            }
        }
    }
    
优点
  • 高性能:结合了悲观锁和乐观锁的优点。
  • 灵活性:支持多种锁模式。
缺点
  • 复杂性:实现和使用相对复杂。

2.5 使用分布式锁

在分布式系统中,可以使用 ZooKeeper 或 Redis 实现悲观锁。

2.5.1 使用 Redis 实现分布式锁

java复制

public class DistributedLockExample {
    private RedisTemplate<String, String> redisTemplate;

    public boolean acquireLock(String lockKey) {
        // 尝试获取锁
        String lockValue = UUID.randomUUID().toString();
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
        return success != null && success;
    }

    public void releaseLock(String lockKey, String lockValue) {
        // 获取当前锁的值
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        if (currentValue != null && currentValue.equals(lockValue)) {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

2.5.2 使用 ZooKeeper 实现分布式锁

java复制

public class DistributedLockExample {
    private CuratorFramework client;

    public void acquireLock(String lockPath) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);
        lock.acquire();
    }

    public void releaseLock(String lockPath) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);
        lock.release();
    }
}
优点
  • 分布式支持:适用于分布式系统。
  • 高一致性:确保分布式环境下的数据一致性。
缺点
  • 依赖外部服务:需要依赖 ZooKeeper 或 Redis 等外部服务。
  • 复杂性:实现和维护成本较高。

总结

悲观锁的实现方式多样,可以根据具体需求选择合适的方式:

  • synchronized:简单易用,适合单线程环境。
  • ReentrantLock:功能丰富,适合需要更多控制的场景。
  • 数据库锁:直接利用数据库的锁机制,适合数据一致性要求高的场景。
  • 分布式锁:适用于分布式系统,确保数据一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值