乐观锁和悲观锁(MySQL和Java)
在并发编程中,为了确保数据的一致性和完整性,我们通常需要使用锁机制来控制对共享资源的访问。锁主要分为两种:乐观锁和悲观锁。本文将详细介绍这两种锁的概念、工作原理以及它们的优缺点。
悲观锁
悲观锁(Pessimistic Lock)是一种假定会发生并发冲突的锁机制,因此在操作数据之前,先将数据锁定,以防止其他线程修改数据。这种锁机制通常用于写操作较多的场景,因为它能确保数据的一致性和完整性。
工作原理
当一个线程想要操作某条数据时,它会首先尝试获取该数据的锁。如果获取成功,其他线程将无法对这条数据进行操作,直到该线程释放锁。
优缺点
- 优点:
- 能够确保数据的强一致性。
- 简单易用,适用于多写操作的场景。
- 缺点:
- 可能导致大量的锁竞争,影响性能。
- 容易引起死锁。
示例
在数据库中,悲观锁通常通过“SELECT … FOR UPDATE”语句来实现。例如:
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
这条语句会在读取数据的同时锁定该行数据,直到事务提交或回滚。
乐观锁
乐观锁(Optimistic Lock)是一种假定不会发生并发冲突的锁机制,它在操作数据时不加锁,而是在提交更新时检查是否有其他线程修改了数据。如果发生冲突,则回滚并重试操作。这种锁机制适用于读操作较多的场景。
工作原理
乐观锁通常通过版本号或时间戳来实现。在更新数据时,检查当前版本号或时间戳是否与读取时一致,如果一致则更新,否则说明数据已被其他线程修改,回滚操作。
优缺点
- 优点:
- 没有锁竞争,性能较高。
- 适用于多读操作的场景。
- 缺点:
- 无法确保数据的强一致性。
- 需要处理重试逻辑,较为复杂。
示例
在数据库中,乐观锁通常通过版本号来实现。例如:
- 查询数据及版本号:
SELECT id, name, version FROM users WHERE id = 1;
- 更新数据时,检查版本号:
UPDATE users SET name = 'newName', version = version + 1 WHERE id = 1 AND version = 1;
如果版本号匹配,则更新成功;否则更新失败,需要重试。
Java中的乐观锁和悲观锁
悲观锁
在Java中,悲观锁通常通过synchronized
关键字或ReentrantLock
类来实现。synchronized
关键字可以用来修饰方法或代码块,而ReentrantLock
类提供了更灵活的锁控制。
// 使用synchronized关键字
public synchronized void method() {
// 临界区代码
}
// 使用ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
乐观锁
在Java中,乐观锁通常通过java.util.concurrent.atomic
包中的类来实现,例如AtomicInteger
、AtomicLong
和AtomicReference
等。这些类使用CAS(Compare And Swap)操作来实现无锁并发控制。
import java.util.concurrent.atomic.AtomicInteger;
private final AtomicInteger version = new AtomicInteger(0);
public void update() {
int currentVersion = version.get();
// 业务逻辑
version.compareAndSet(currentVersion, currentVersion + 1);
}
MySQL中的乐观锁和悲观锁
在MySQL中,乐观锁和悲观锁主要通过事务控制和特定的SQL语句来实现。
悲观锁
在MySQL中,可以通过SELECT ... FOR UPDATE
语句来实现悲观锁。例如:
START TRANSACTION;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 执行需要锁定的操作
COMMIT;
乐观锁
乐观锁通常通过版本号或时间戳字段来实现。例如,在更新数据时,可以检查版本号是否一致:
- 查询数据及版本号:
SELECT id, name, version FROM users WHERE id = 1;
- 更新数据时,检查版本号:
UPDATE users SET name = 'newName', version = version + 1 WHERE id = 1 AND version = 1;
如果版本号匹配,则更新成功;否则更新失败,需要重试。
参考链接
- MySQL 悲观锁:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
- MySQL 乐观锁:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
- Java并发:https://docs.oracle.com/javase/tutorial/essential/concurrency/