锁原理和使用
在 Java 和数据库开发中,不同类型的锁被广泛应用来确保数据一致性和线程安全。以下是常用锁的分类及其实现原理详解,并提供了相应的示例。
1. 公平锁和非公平锁
- 公平锁:线程按请求顺序获取锁,避免“插队”。
- 非公平锁:允许线程在队列中“插队”,可能导致部分线程长时间等待。
实现方式:ReentrantLock
支持公平和非公平锁,默认是非公平锁。
// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁
ReentrantLock nonFairLock = new ReentrantLock(false);
2. 可重入锁
可重入锁允许同一线程多次获取同一锁,防止因自己再次获取锁而导致死锁。ReentrantLock
和 synchronized
都是可重入锁。
public class ReentrantExample {public synchronized void methodA() {methodB(); // 当前线程可以再次进入}public synchronized void methodB() {// 逻辑代码}
}
3. 递归锁
递归锁允许线程在持有锁的情况下递归调用获取该锁。它是可重入锁的实现方式之一。ReentrantLock
是递归锁的典型实现。
4. 自旋锁
自旋锁在等待获取锁时,不立即阻塞线程,而是通过循环尝试。适合锁的持有时间较短的场景,减少了上下文切换的开销。
public class SpinLock {private AtomicReference<Thread> owner = new AtomicReference<>();public void lock() {Thread current = Thread.currentThread();while (!owner.compareAndSet(null, current)) {// 自旋等待}}public void unlock() {Thread current = Thread.currentThread();owner.compareAndSet(current, null);}
}
5. 读写锁
读写锁分为读锁和写锁,多个线程可以同时读取数据,但写操作是独占的。典型实现是 ReentrantReadWriteLock
。
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
try {// 读取操作
} finally {lock.readLock().unlock();
}lock.writeLock().lock();
try {// 写入操作
} finally {lock.writeLock().unlock();
}
6. 悲观锁和乐观锁
- 悲观锁:假定并发冲突频繁,数据操作前即上锁,如数据库的
SELECT ... FOR UPDATE
。 - 乐观锁:假定并发冲突较少,通过版本号等机制在提交时验证数据是否被修改。
// 乐观锁示例
public void updateWithOptimisticLock(int version) {// 更新时检查 version 是否相同String sql = "UPDATE table SET column = ? WHERE version = ?";
}
7. 行锁和表锁
- 行锁:锁定特定行,如 InnoDB 支持行锁。
- 表锁:锁定整个表,MyISAM 仅支持表锁。
行锁更适合高并发写操作,表锁适合大量读操作的场景。
8. 排它锁和共享锁
- 排它锁(独占锁):同一时间只允许一个事务访问数据。
- 共享锁:允许多个事务同时读取数据,但不允许写入。
SELECT ... LOCK IN SHARE MODE
创建共享锁。
9. 死锁
死锁是指两个或多个线程相互持有对方需要的锁,导致永久等待。避免死锁的方法包括:
- 保持锁定顺序一致
- 使用定时锁,尝试锁超时失败
- 减少锁的持有时间
10. 分布式锁
分布式锁用于确保分布式系统中资源的独占性。Redis 和 Zookeeper 是常用的实现工具。典型的 Redis 分布式锁示例:
// Redis分布式锁示例
String lockKey = "resource_lock";
String requestId = UUID.randomUUID().toString();// 加锁
boolean lock = redis.set(lockKey, requestId, "NX", "PX", 30000); // NX:不存在时设置, PX:设置超时// 解锁
if (requestId.equals(redis.get(lockKey))) {redis.del(lockKey);
}
11. AQS(AbstractQueuedSynchronizer)
AQS
是 Java 提供的一个用于构建锁和同步器的底层框架,基于一个先进先出的队列来管理锁的获取和释放。ReentrantLock
、Semaphore
、CountDownLatch
等类均基于 AQS 实现。
12. Synchronized 原理
synchronized
是 Java 的内置锁,通过对象监视器实现。其底层是基于 Monitor 锁机制,每个对象包含一个 Monitor,实现方法包括:
- 对象头的 Mark Word:记录线程的锁定状态。
- 锁升级机制:从偏向锁、轻量级锁到重量级锁,以适应不同并发量的场景。
示例:
public synchronized void syncMethod() {// 方法体
}
13. 锁的使用示例
// 使用ReentrantLock可重入锁示例
public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();public void performTask() {lock.lock();try {System.out.println("任务执行中...");} finally {lock.unlock();}}
}// 使用Synchronized实现同步块
public class SynchronizedExample {public void synchronizedTask() {synchronized (this) {System.out.println("同步任务执行中...");}}
}
总结
在高并发编程和分布式系统中,不同锁的特性决定了其适用场景。了解各种锁的原理和使用方法,能够帮助开发者在不同场景中选择合适的锁机制,从而实现线程安全并优化性能。