ReentrantLock 与 ReentrantReadWriteLock 的区别与用法
1. ReentrantLock
ReentrantLock
是一种可重入互斥锁,它提供了与 synchronized
相同的基本行为和语义,但功能更加强大。其特点包括:
- 可响应性:锁可以由未持有锁的线程释放,这减少了锁不必要的保持时间。
- 可中断性:一个正在等待锁的线程可以被中断。
- 公平性:锁可以设置为公平锁或非公平锁。
-
条件变量:
ReentrantLock
配合Condition
接口提供了比Object
的wait()
、notify()
和notifyAll()
方法更强大的等待/通知机制。
示例代码
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Counter counter = new Counter();
for (int i = 0; i < 10; i++) {
new Thread(() -> counter.add(1)).start();
}
}
}
2. ReentrantReadWriteLock
ReentrantReadWriteLock
是一种读写锁,允许多个读线程同时访问,但只允许一个写线程访问,或者阻塞所有的读写线程。这种锁的设计可以提高性能,特别是在数据结构中,读操作的数量远远超过写操作的情况下。
示例代码
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Counter {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
private int count;
public int getCount() {
r.lock();
try {
return count;
} finally {
r.unlock();
}
}
public void inc() {
w.lock();
try {
count++;
} finally {
w.unlock();
}
}
public static void main(String[] args) {
Counter counter = new Counter();
for (int i = 0; i < 10; i++) {
new Thread(() -> counter.inc()).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(counter.getCount())).start();
}
}
}
3. 区别
-
锁的类型:
-
ReentrantLock
是一个互斥锁,同一时间只有一个线程可以持有锁。 -
ReentrantReadWriteLock
是一个读写锁,允许多个读线程同时访问,但写线程独占访问。
-
-
性能:
-
ReentrantLock
适用于读写操作频繁且写操作较多的场景。 -
ReentrantReadWriteLock
适用于读操作远多于写操作的场景,可以显著提高并发性能。
-
-
功能:
-
ReentrantLock
提供了更多的功能,如可中断的锁定、尝试锁定、条件变量等。 -
ReentrantReadWriteLock
提供了读锁和写锁,支持锁降级等高级功能。
-
案例分析
假设有一个共享的数据结构,读操作远多于写操作。使用 ReentrantReadWriteLock
可以显著提高并发性能,因为多个读线程可以同时访问数据,而写线程则独占访问。以下是一个具体的例子:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CachedData {
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private Object data;
private boolean cacheValid;
public void processCachedData() {
rwl.readLock().lock();
try {
if (!cacheValid) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (!cacheValid) {
data = fetchDataFromDatabase();
cacheValid = true;
}
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock();
}
}
use(data);
} finally {
rwl.readLock().unlock();
}
}
private Object fetchDataFromDatabase() {
// 模拟从数据库获取数据
return new Object();
}
private void use(Object data) {
// 模拟使用数据
System.out.println("使用数据: " + data);
}
public static void main(String[] args) {
CachedData cachedData = new CachedData();
for (int i = 0; i < 10; i++) {
new Thread(() -> cachedData.processCachedData()).start();
}
}
}
在这个例子中,processCachedData
方法首先尝试获取读锁。如果缓存无效,它会释放读锁并获取写锁来更新缓存。更新完成后,它会进行写锁到读锁的降级,允许其他线程并发读取。
通过这种方式,ReentrantReadWriteLock
可以在确保数据一致性的同时,提高多线程环境下的性能。