排他锁(Exclusive Lock),也称为写锁或互斥锁,是一种严格的并发控制机制,确保同一时间只有一个线程能够访问和修改共享资源。其他线程在锁被释放前,无法进行读或写操作,从而保证数据的一致性和完整性。
核心原理
独占访问
- 线程获取排他锁后,独占资源,禁止其他线程的读写请求。
- 适用于写操作,如数据更新、删除等场景。
锁的互斥性
- 排他锁与任何其他锁(包括共享锁、其他排他锁)互斥。
- 必须等待当前锁释放后,其他线程才能获取锁。
排他锁的优缺点
优点
- 强一致性:确保数据修改的原子性,避免脏写和数据竞争。
- 实现简单:多数编程语言和数据库提供内置支持。
缺点
- 并发度低:同一时间仅一个线程可操作,可能导致性能瓶颈。
- 死锁风险:多线程循环等待锁时可能产生死锁。
实现方式
1. 数据库层面
- MySQL:
SELECT * FROM table WHERE id = ? FOR UPDATE; -- 排他锁
- 事务提交前,其他事务无法读取或修改该行数据。
2. Java语言实现
synchronized关键字:
public class SynchronizedExample { private final Object lock = new Object(); public void writeData() { synchronized (lock) { // 临界区代码,同一时间仅一个线程执行 } } }
ReentrantLock:
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); public void writeData() { lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须在finally中释放锁 } } }
ReadWriteLock的写锁:
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class WriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void writeData() { rwLock.writeLock().lock(); // 获取写锁(排他锁) try { // 写操作,禁止其他线程读写 } finally { rwLock.writeLock().unlock(); } } }
排他锁的典型应用场景
数据更新操作
- 如库存扣减、账户转账,确保原子性。
文件写入
- 避免多线程同时修改文件导致数据错乱。
临界资源访问
- 如全局计数器、配置文件修改。
与其他锁的对比
锁类型 | 读并发 | 写并发 | 互斥对象 | 适用场景 |
---|---|---|---|---|
排他锁 | 独占 | 独占 | 读锁、写锁 | 写操作为主,如账户扣款 |
共享锁 | 允许多个 | 独占 | 写锁 | 读多写少,如数据库查询 |
乐观锁 | 无锁 | CAS检查 | 无(冲突重试) | 冲突少的读多写少场景 |
悲观锁 | 独占 | 独占 | 所有操作 | 冲突多的写操作场景 |
注意事项
死锁预防
- 按相同顺序获取锁,设置锁超时时间。
- 使用
ReentrantLock.tryLock()
避免死锁。
锁粒度优化
- 避免在大方法上加锁,减小锁的范围。
性能监控
- 高并发场景下,通过工具(如JStack)监控锁竞争情况。
Java中的排他锁工具
synchronized关键字
- 隐式锁,由JVM自动释放。
ReentrantLock
- 显式锁,支持公平锁、可中断锁、条件变量。
ReadWriteLock的写锁
- 与读锁配合使用,实现读写分离。
总结
排他锁通过独占资源确保数据一致性,适用于写操作频繁、对数据完整性要求高的场景。但在高并发环境下,过度使用会导致性能瓶颈。现代系统通常结合排他锁与其他锁机制(如共享锁、乐观锁),根据业务特点选择合适的并发控制策略,以平衡性能与数据安全。