乐观锁与悲观锁

乐观锁和悲观锁是处理数据库并发操作的两种不同策略

乐观锁

乐观锁的核心思想是“乐观”,它假设在数据处理过程中,冲突发生的概率较低。因此,乐观锁不会在事务开始时就锁定数据,而是在数据提交时检查是否有其他事务修改过这些数据。如果数据未被修改,则事务可以成功提交;如果数据被其他事务修改了,则当前事务需要重新执行或放弃。乐观锁通常通过版本号(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子句获取了悲观锁,确保在事务执行期间不会有其他事务对数据进行修改。然后,我们执行查询操作并获取用户的余额信息。接下来,我们更新余额并提交事务。如果在事务执行期间有其他事务尝试修改同一行数据,将会被阻塞直到当前事务完成。 

  • 56
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值