乐观锁和悲观锁是两种处理并发控制的不同策略,它们各有侧重,适用于不同的场景。下面我会用生活化的例子来帮助你理解这两种锁机制,并展示在Java中如何简单地实现它们。
乐观锁
概念:乐观锁假定在大部分情况下,数据不会发生并发冲突,所以在操作数据前不会加锁,只是在更新数据的时候检查在此期间数据是否被其他事务修改过。乐观锁的核心在于"先做事,后检查"。
例子:想象你和朋友一起去图书馆借书,乐观锁的策略就像是你认为这本书多半没人借,所以你直接去前台登记借书,登记时再确认书有没有被别人借走。如果书被借走了,你就重新找一本或者等待。
Java实现:乐观锁通常借助版本号或时间戳来实现。比如,我们在数据库表中添加一个版本字段version,每次更新数据时,同时更新版本号。
1public class Book {
2 private int id;
3 private String title;
4 private int version; // 版本号
5
6 // 更新书籍的方法,使用乐观锁
7 public boolean updateTitle(String newTitle) {
8 // 假设这里有一段SQL,类似 "UPDATE book SET title = ?, version = version + 1 WHERE id = ? AND version = ?"
9 // 如果更新的行数为0,说明版本号不对,即数据已被其他人修改
10 int updatedRows = dao.updateTitleAndVersion(id, newTitle, version);
11 if (updatedRows > 0) {
12 version++; // 如果更新成功,本地版本号也需要+1
13 return true;
14 }
15 return false;
16 }
17}
悲观锁
概念:悲观锁采取较为保守的态度,认为数据在操作过程中很可能会被其他事务修改,因此在操作数据前就会加锁,阻止其他事务的修改操作,直到当前事务完成。悲观锁的核心思想是"先拿锁,后做事"。
例子:还是借书的情景,悲观锁就像是你担心书随时会被别人借走,所以你一开始就拿着这本书去柜台排队,直到借书手续完成才放手,期间别人不能借走这本书。
Java实现:在Java中,悲观锁的实现通常依赖于数据库层面的锁机制,如行级锁。使用JDBC时,可以通过设置事务隔离级别和使用SELECT ... FOR UPDATE来实现悲观锁。
1Connection conn = dataSource.getConnection();
2try {
3 conn.setAutoCommit(false); // 开启事务
4 PreparedStatement ps = conn.prepareStatement("SELECT * FROM book WHERE id = ? FOR UPDATE");
5 ps.setInt(1, bookId);
6 ResultSet rs = ps.executeQuery();
7 if (rs.next()) {
8 // 处理数据,比如检查库存、更新信息等
9 // ...
10
11 // 执行更新操作
12 // ...
13
14 conn.commit(); // 提交事务
15 }
16} catch (SQLException e) {
17 conn.rollback(); // 出错回滚事务
18} finally {
19 conn.close();
20}
- 乐观锁适用于并发冲突较少的场景,它通过版本控制减少锁定时间,提高并发性能,但需要额外的逻辑来处理冲突。
- 悲观锁则适用于并发冲突频繁的场景,它通过提前加锁确保数据一致性,但可能会导致较高的锁竞争和等待时间,影响并发性能。
选择哪种锁,取决于你对数据并发访问的预期和对性能的要求。在实际应用中,还需要根据具体业务场景和数据库特性综合考虑。