一文讲透 Java 和数据库中的锁机制
锁是并发编程和数据库管理中保证数据一致性与安全性的核心机制。它们虽存在于不同层面,但原理相似:在资源被使用时,控制访问权,避免冲突和数据错误。
一、Java 中的锁机制
1️⃣ 锁的目的
- 保证多线程访问共享资源时的数据一致性
- 避免竞态条件(race condition)
- 控制线程同步和调度
2️⃣ 锁的分类
(1)悲观锁(Pessimistic Lock)
- 概念:假设其他线程会并发修改,所以操作前就加锁。
-
实现方式:
-
synchronized
(Java 内置锁) -
ReentrantLock
(可重入锁)
-
-
特点:
- 阻塞式,线程需要等待锁释放
- 适合写多冲突大的场景
示例:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
(2)乐观锁(Optimistic Lock)
- 概念:假设其他线程不会冲突,操作时检查是否被修改。
-
实现方式:
- 版本号控制(version field)
- CAS(Compare-And-Swap / Compare-And-Set)
-
特点:
- 无锁或轻量锁
- 适合读多写少的场景
示例(版本号):
class OptimisticCounter {
private int count = 0;
private int version = 0;
public boolean increment() {
int oldVersion = version;
int oldCount = count;
int newCount = oldCount + 1;
if (oldVersion == version) {
count = newCount;
version++;
return true;
} else {
return false; // 冲突,需要重试
}
}
}
(3)CAS(Compare-And-Swap)
- 概念:原子操作,通过比较旧值和期望值来决定是否更新
-
特点:
- 无锁,性能高
- 可能发生 ABA 问题,需要版本号解决
示例(AtomicInteger):
import java.util.concurrent.atomic.AtomicInteger;
class CASCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue));
}
public int getCount() {
return count.get();
}
}
(4)其他锁
锁类型 | 说明 | 适用场景 |
---|---|---|
读写锁(ReadWriteLock) | 允许多个线程读,但写互斥 | 读多写少 |
偏向锁、轻量锁、重量锁(JVM 锁优化) | JVM 内部锁优化 | 减少无冲突锁的开销 |
二、数据库中的锁机制
数据库锁是为了保证事务的 ACID 特性(特别是隔离性),常用在关系型数据库如 MySQL、Oracle、PostgreSQL。
1️⃣ 锁的目的
- 防止脏读、不可重复读、幻读
- 保证事务隔离和数据一致性
2️⃣ 锁的分类
(1)按加锁粒度
- 表级锁(Table Lock):锁整个表,简单但性能低
- 行级锁(Row Lock / Record Lock):只锁某行,性能高但开销大
(2)按锁类型
类型 | 作用 | 典型用途 |
---|---|---|
排它锁(Exclusive / X Lock) | 只允许自己读写 | 写操作 |
共享锁(Shared / S Lock) | 多个事务可以共享读 | 读操作 |
意向锁(Intention Lock) | 表示事务将要在行上加锁 | 行级锁配合表锁使用 |
乐观锁 | 通过版本号或时间戳检查冲突 | 适合高并发更新 |
悲观锁 | 使用 SELECT … FOR UPDATE
|
防止并发修改 |
(3)数据库锁机制示例(MySQL InnoDB)
- 悲观锁:
SELECT * FROM users WHERE id=1 FOR UPDATE;
-- 给查询行加排它锁,直到事务提交
- 乐观锁:
UPDATE users
SET balance = balance + 100, version = version + 1
WHERE id = 1 AND version = 5;
-- version 检查是否被其他事务修改
三、Java 锁 vs 数据库锁对比
维度 | Java 锁 | 数据库锁 |
---|---|---|
锁的对象 | 内存中的对象 | 数据库表、行 |
控制方式 | JVM / OS | 数据库引擎 |
粒度 | 对象、字段、方法 | 表、行 |
实现机制 | synchronized、ReentrantLock、CAS | X/S 锁、行锁、表锁、MVCC |
适用场景 | 并发编程、共享内存 | 事务管理、并发数据操作 |
性能 | 高(轻量锁) | 较低(I/O 开销大) |
四、总结与实用建议
- Java 层:
- 写多用悲观锁,读多写少用乐观锁 + CAS
-
尽量使用原子类(
AtomicInteger
)或ReadWriteLock
提升性能- 数据库层:
读操作尽量少用锁,使用 MVCC(MySQL InnoDB 默认)
-
写操作冲突大时使用悲观锁,少量并发可用乐观锁
- 全链路设计:
高并发系统,Java 层用 CAS/乐观锁 + 数据库乐观锁
写冲突低,优先乐观锁提高吞吐量
Top comments (0)