当前位置:首页 > 后端开发 > 正文

如何在Java中添加行锁?

在Java中,行锁通常通过synchronized关键字或ReentrantLock实现,synchronized修饰方法或代码块,确保同一时刻仅一个线程执行;ReentrantLock需手动lock()/unlock(),提供更灵活的锁控制。

在Java中实现”行锁”通常有两种场景:代码层面的对象锁(模拟行锁行为)和数据库行锁,下面从技术原理、实现方式和最佳实践进行详细说明,确保内容符合专业性与可靠性标准。


代码层面的对象锁(模拟行锁)

当需要锁定单个对象实例(类比数据库中的一行数据)时,可通过以下方式实现:

synchronized 关键字

public class Account {
    private final Object lock = new Object(); // 专用锁对象
    private int balance;
    public void transfer(Account target, int amount) {
        // 锁定当前对象(模拟行锁)
        synchronized (this) {
            // 锁定目标对象
            synchronized (target) {
                this.balance -= amount;
                target.balance += amount;
            }
        }
    }
}

原理

  • 每个对象内置一个监视器锁(Monitor),synchronized 基于此实现互斥。
  • 锁定 this 或自定义锁对象,相当于对”行”(对象实例)加锁。

注意事项

如何在Java中添加行锁?  第1张

  • 避免嵌套锁导致的死锁(如上例需按固定顺序加锁)。
  • 锁范围应尽量缩小(仅在临界区加锁)。

ReentrantLock 显式锁

import java.util.concurrent.locks.ReentrantLock;
public class RowLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int data;
    public void updateData(int value) {
        lock.lock(); // 加锁
        try {
            data = value; // 临界区操作
        } finally {
            lock.unlock(); // 必须释放锁
        }
    }
}

优势

  • 支持公平锁、超时尝试(tryLock)、可中断等待。
  • 更灵活的锁控制。

数据库行锁(Java中操作)

当锁的目标是数据库中的行记录时,需通过SQL实现:

悲观锁(SELECT FOR UPDATE

// JDBC 示例(以MySQL为例)
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
conn.setAutoCommit(false);
try {
    // 锁定id=100的行
    PreparedStatement stmt = conn.prepareStatement(
        "SELECT * FROM accounts WHERE id = ? FOR UPDATE"
    );
    stmt.setInt(1, 100);
    ResultSet rs = stmt.executeQuery();
    // 更新操作
    PreparedStatement updateStmt = conn.prepareStatement(
        "UPDATE accounts SET balance = balance - 100 WHERE id = ?"
    );
    updateStmt.setInt(1, 100);
    updateStmt.executeUpdate();
    conn.commit(); // 提交事务释放锁
} catch (SQLException e) {
    conn.rollback();
}

关键点

  • FOR UPDATE 在事务中锁定查询到的行。
  • 事务提交/回滚后自动释放锁。
  • 需配合事务隔离级别(推荐 READ_COMMITTEDREPEATABLE_READ)。

乐观锁(版本控制)

// 更新时检查版本号
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 123 AND version = 5;

原理

  • 读取数据时记录版本号(或时间戳)。
  • 更新时校验版本号,失败则重试或抛异常。
  • 适合低冲突场景,避免长期锁竞争。

如何选择锁方案?

场景 推荐方案
单JVM内的对象互斥 synchronizedReentrantLock
高并发数据库写操作 悲观锁(FOR UPDATE
读多写少、冲突概率低 乐观锁(版本控制)

避坑指南

  1. 死锁预防

    • 代码锁:按固定顺序获取锁(如按对象ID排序)。
    • 数据库锁:设置锁超时(innodb_lock_wait_timeout)。
  2. 性能优化

    • 缩小锁范围(减少临界区代码)。
    • 无锁化设计(如 ConcurrentHashMap)。
  3. 分布式环境

    • 使用分布式锁(Redis Redlock、ZooKeeper)。
    • 避免数据库行锁跨服务失效。

  • 代码级”行锁”:通过 synchronizedReentrantLock 锁定对象实例。
  • 数据库行锁:依赖SQL的 FOR UPDATE 或乐观锁实现。
  • 核心原则:最小化锁范围、预防死锁、匹配业务场景。

引用说明

  • Oracle官方Java并发指南:Threads and Locks
  • MySQL行锁机制:InnoDB Locking
  • 并发实践参考:《Java并发编程实战》(Brian Goetz著)
0