悲观锁、乐观锁、公平锁和非公平锁的原理与使用
悲观锁、乐观锁、公平锁、非公平锁的原理与使用
概述
在并发编程中,锁是用来保护临界区资源的一种机制。悲观锁、乐观锁、公平锁和非公平锁是常见的锁类型,它们在不同的场景下具有不同的特点和优劣势。本文将详细介绍这四种锁的原理、使用方法和适用场景。
1. 悲观锁
悲观锁认为在整个访问过程中都会发生并发冲突,因此在每次访问共享资源之前都会将其锁定,使其他线程无法访问。常见的悲观锁实现方式是使用互斥锁(Mutex)或者读写锁(ReadWriteLock)。Java中的悲观锁主要通过synchronized
关键字和Lock
接口的实现类来实现。其中synchronized
关键字是隐式锁(互斥锁),而Lock
接口的实现类(如ReentrantLock
)是显示锁。
悲观锁的示例代码:
使用synchronized
来实现悲观锁的示例如下:
public synchronized void synchronizedMethod() {
// 访问共享资源的代码
}
使用ReentrantLock
来实现悲观锁的示例如下:
Lock lock = new ReentrantLock();
public void lockMethod() {
lock.lock();
try {
// 访问共享资源的代码
} finally {
lock.unlock();
}
}
悲观锁的特点:
- 悲观锁适用于多写少读的场景,可以保证数据的一致性。
- 悲观锁在多线程竞争下性能较低,因为每次访问共享资源都需要加锁和解锁的操作。
2. 乐观锁
乐观锁认为在整个访问过程中不会发生并发冲突,因此不需要对共享资源加锁,而是在更新数据时通过版本号或者CAS(Compare And Swap)操作来保证数据的一致性。在Java中,Atomic
包下的原子类(如AtomicInteger
、AtomicReference
等)就是使用乐观锁的例子。
乐观锁的示例代码:
import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockExample {
private final AtomicInteger version = new AtomicInteger(0);
private int resource;
public void accessResource() {
int currentVersion = version.get();
// 读取共享资源
// 更新共享资源
if (version.compareAndSet(currentVersion, currentVersion + 1)) {
// 更新成功
// 其他操作
} else {
// 更新失败
// 重试或者进行其他处理
}
}
}
乐观锁的特点:
- 乐观锁适用于多读少写的场景,可以提高并发性能。
- 乐观锁的实现方式相对复杂,需要考虑数据一致性和并发冲突的处理。
3. 公平锁
公平锁是指多个线程按照请求的顺序获取锁资源,即先到先得。使用公平锁可以避免某些线程长时间等待的情况,但也会引入一定的线程切换开销。 Java中的synchronized
关键字是一种非公平锁。 Java中的ReentrantLock
可以通过构造函数的参数来指定锁的公平性。默认情况下,ReentrantLock
是非公平锁。
Lock fairLock = new ReentrantLock(true); // 公平锁
Lock unfairLock = new ReentrantLock(false); // 非公平锁
公平锁的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private final ReentrantLock lock = new ReentrantLock(true);
public void accessResource() {
try {
lock.lock(); // 获取锁
// 访问共享资源
} finally {
lock.unlock(); // 释放锁
}
}
}
公平锁的特点:
- 公平锁可以确保线程按照请求顺序获取锁资源,避免饥饿现象。
- 公平锁的性能较悲观锁和乐观锁稍低,因为需要维护等待队列和进行线程切换。
4. 非公平锁
非公平锁是指多个线程获取锁的顺序是不确定的,可能出现新请求的线程抢占锁资源的情况。使用非公平锁可以提高吞吐量和性能,但可能会导致某些线程长时间等待。
非公平锁的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class UnfairLockExample {
private final ReentrantLock lock = new ReentrantLock(false);
public void accessResource() {
try {
lock.lock(); // 获取锁
// 访问共享资源
} finally {
lock.unlock(); // 释放锁
}
}
}
非公平锁的特点:
- 非公平锁允许新请求的线程抢占锁资源,可以提高吞吐量和性能。
- 非公平锁可能导致某些线程长时间等待,可能会引起线程饥饿现象。
总结
悲观锁、乐观锁、公平锁和非公平锁是常见的并发编程锁类型。它们在不同的场景下有不同的特点和优劣势。悲观锁适用于多写少读的场景,可以保证数据的一致性;乐观锁适用于多读少写的场景,可以提高并发性能;公平锁可以确保线程按照请求顺序获取锁资源,避免饥饿现象;非公平锁可以提高吞吐量和性能,但可能会导致某些线程长时间等待。
在实际应用中,需要根据具体的场景选择合适的锁类型。同时,锁的使用要尽量精确地控制临界区范围,避免死锁和性能问题。