乐观锁和悲观锁是处理数据库并发操作的两种不同策略
乐观锁:
乐观锁的核心思想是“乐观”,它假设在数据处理过程中,冲突发生的概率较低。因此,乐观锁不会在事务开始时就锁定数据,而是在数据提交时检查是否有其他事务修改过这些数据。如果数据未被修改,则事务可以成功提交;如果数据被其他事务修改了,则当前事务需要重新执行或放弃。乐观锁通常通过版本号(version)或时间戳(timestamp)来实现,每次更新数据时,版本号或时间戳都会相应地增加。这样,当事务尝试更新数据时,它会检查版本号或时间戳是否与开始时相同,如果不同则说明数据已被其他事务修改。
import java.util.concurrent.TimeUnit;
public class OptimisticLockExample {
private static int balance = 100;
private static int version = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> updateBalance(50));
Thread thread2 = new Thread(() -> updateBalance(30));
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final balance: " + balance);
}
public static void updateBalance(int amount) {
int currentVersion = version;
// 模拟业务逻辑,更新余额
int newBalance = balance + amount;
// 模拟其他事务可能对数据进行修改的情况
try {
TimeUnit.SECONDS.sleep(2); // 模拟等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 检查版本号是否发生变化
if (version == currentVersion) {
// 没有变化,可以更新数据
balance = newBalance;
version++;
System.out.println("Balance updated successfully!");
} else {
// 版本号发生变化,说明有其他事务修改了数据,需要回滚
System.out.println("Data has been modified by another transaction. Rollback.");
}
}
}
悲观锁:
悲观锁是一种预防性的策略,它的核心思想是在数据被访问时加锁,以防止其他事务或进程同时修改同一数据。这通常通过数据库提供的锁机制来实现,确保在任一时刻只有一个事务能够对数据进行写操作。
悲观锁的实现通常涉及到数据库中的行级锁或表级锁。行级锁是锁定特定行,而表级锁则是锁定整张表。悲观锁在数据被读取时就加上锁,直到事务结束才会释放,这样可以保证在事务执行期间不会有其他事务对数据进行修改。这种机制适用于写操作频繁、冲突概率高的环境,因为它可以有效地防止冲突发生,但可能会影响并发性能。
与乐观锁相比,悲观锁在数据处理上更为保守,总是假设共享资源会被修改,因此它在数据操作前就加上锁。乐观锁则相反,它假设共享资源不会被修改,只在提交时验证。
悲观锁是一种更为保守的并发控制策略,适用于对数据一致性要求较高的情景。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PessimisticLockExample {
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
private static final String DB_USER = "username";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
connection.setAutoCommit(false); // 关闭自动提交
// 获取悲观锁
String selectQuery = "SELECT * FROM users WHERE id = 1 FOR UPDATE";
PreparedStatement selectStatement = connection.prepareStatement(selectQuery);
ResultSet resultSet = selectStatement.executeQuery();
if (resultSet.next()) {
int balance = resultSet.getInt("balance");
int newBalance = balance + 50;
// 更新余额
String updateQuery = "UPDATE users SET balance = ? WHERE id = 1";
PreparedStatement updateStatement = connection.prepareStatement(updateQuery);
updateStatement.setInt(1, newBalance);
updateStatement.executeUpdate();
connection.commit(); // 提交事务
System.out.println("Balance updated successfully!");
} else {
System.out.println("User not found.");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
上述示例中,我们使用了MySQL数据库作为示例。首先,我们通过FOR UPDATE
子句获取了悲观锁,确保在事务执行期间不会有其他事务对数据进行修改。然后,我们执行查询操作并获取用户的余额信息。接下来,我们更新余额并提交事务。如果在事务执行期间有其他事务尝试修改同一行数据,将会被阻塞直到当前事务完成。