分布式微服务系统架构第141集:锁

72 阅读3分钟

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc…

1024bat.cn/

github.com/webVueBlog/…

webvueblog.github.io/JavaPlusDoc…

1. 乐观锁(版本号)——库存扣减示例

假设表 productidstockversion 字段,使用 version 做乐观锁:

@Service
public class InventoryService {

    @Autowired
    private JdbcTemplate jdbc;

    public boolean decreaseStock(Long productId) {
        // 1. 读取当前版本号和库存
        Map<String, Object> row = jdbc.queryForMap(
            "SELECT stock, version FROM product WHERE id = ?", productId);
        int stock = (int) row.get("stock");
        int version = (int) row.get("version");
        if (stock <= 0) {
            return false;
        }
        // 2. 尝试更新:WHERE id=? AND version=?
        int updated = jdbc.update(
            "UPDATE product SET stock = stock - 1, version = version + 1 " +
            "WHERE id = ? AND version = ?", productId, version);
        // 3. updated==1 成功,否则认为被并发修改,返回失败
        return updated == 1;
    }
}
  • 阶段:读取→校验(在 SQL 的 WHERE 里)→写入
  • 冲突处理:返回 false,可由上层决定重试或提示用户

2. 悲观锁(SELECT FOR UPDATE)——订单处理示例

在一个事务中对库存行加行级锁,避免并发超卖:

@Service
public class OrderService {

    @Autowired
    private DataSource dataSource;

    @Transactional
    public void placeOrder(Long productId, int qty) {
        // 1. SELECT ... FOR UPDATE 会锁定该行直到事务提交
        Integer stock = new JdbcTemplate(dataSource)
            .queryForObject(
              "SELECT stock FROM product WHERE id = ? FOR UPDATE",
              Integer.class, productId);

        if (stock == null || stock < qty) {
            throw new InsufficientStockException();
        }
        // 2. 扣减
        new JdbcTemplate(dataSource).update(
          "UPDATE product SET stock = stock - ? WHERE id = ?",
          qty, productId);
        // 3. 保存订单等业务逻辑...
    }
}
  • 锁范围:从 SELECT FOR UPDATE 开始直到事务结束
  • 注意:长事务会阻塞其他并发请求

3. 分布式锁(Redisson)——分布式库存或任务调度

利用 Redisson 在 Redis 上实现分布式锁:

@Service
public class DistributedInventoryService {

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private InventoryService inventoryService;

    public boolean safeDecrease(Long productId) {
        RLock lock = redisson.getLock("lock:product:" + productId);
        // 尝试加锁,等待最多 100ms,锁自动释放时间 5s
        try {
            if (lock.tryLock(100, 5, TimeUnit.SECONDS)) {
                return inventoryService.decreaseStock(productId);
            } else {
                throw new LockAcquisitionException();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
  • 高可用:Redisson 支持可重入、看门狗续期
  • 适合:分布式集群 QPS 较高的场景

4. 可重入锁(ReentrantLock)——临界资源保护

保护一段需要互斥访问的业务逻辑:

@Service
public class ReportService {

    private final ReentrantLock lock = new ReentrantLock();

    public void generateReport() {
        lock.lock();
        try {
            // 临界区:只能一个线程生成报告
            doGenerate();
        } finally {
            lock.unlock();
        }
    }

    private void doGenerate() {
        // 具体生成逻辑,可能会递归调用 generateReport()
    }
}
  • 特点:同一线程可重复获取、支持中断、超时试锁

5. 自旋锁——轻量级互斥示例

简单自旋锁实现,适合持锁时间极短的场景:

public class SimpleSpinLock {
    private final AtomicBoolean flag = new AtomicBoolean(false);

    public void lock() {
        while (!flag.compareAndSet(false, true)) {
            // 自旋等待,不让线程进入阻塞
        }
    }

    public void unlock() {
        flag.set(false);
    }
}

// 使用示例
public class SpinLockService {
    private final SimpleSpinLock spinLock = new SimpleSpinLock();

    public void doWork() {
        spinLock.lock();
        try {
            // 临界区
        } finally {
            spinLock.unlock();
        }
    }
}

6. 读写锁(ReentrantReadWriteLock)——缓存读写场景

@Service
public class CacheService {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Map<String, String> cache = new HashMap<>();

    public String read(String key) {
        rwLock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void write(String key, String value) {
        rwLock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}
  • 读共享:多个线程可同时读
  • 写独占:写时阻塞所有读/写

7. 信号量(Semaphore)——限流示例

@Service
public class UploadService {
    // 最多允许 3 个并发上传
    private final Semaphore sem = new Semaphore(3);

    public void upload(Object data) {
        try {
            sem.acquire();
            doUpload(data);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            sem.release();
        }
    }
}

8. 条件变量(Condition)——生产者-消费者示例

public class ProducerConsumer {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Queue<String> queue = new LinkedList<>();

    public void produce(String item) {
        lock.lock();
        try {
            queue.offer(item);
            notEmpty.signal();  // 通知消费者
        } finally {
            lock.unlock();
        }
    }

    public String consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();  // 释放锁并等待
            }
            return queue.poll();
        } finally {
            lock.unlock();
        }
    }
}

9. 行级锁(数据库内部)——状态流转示例

// 状态只能从 INIT → DOING,重复流转失败
boolean ok = jdbcTemplate.update(
    "UPDATE task SET status='DOING' " +
    "WHERE id=? AND status='INIT'", taskId) == 1;
if (!ok) {
    throw new IllegalStateException("状态流转失败");
}
  • 行级锁:由数据库引擎在该记录更新时自动加/释

10. 分段锁(ConcurrentHashMap)——无须额外代码

JDK7/8 的 ConcurrentHashMap 内部通过多段或节点 + CAS 实现高并发访问,无需额外锁管理,直接使用即可:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.computeIfPresent("key", (k,v) -> v+1);