1. 简单介绍
在Java
中,读写锁(ReadWriteLock
)是一种高级锁机制,允许多个线程同时读取共享资源,而写入共享资源时需要独占访问。读写锁的主要目的是提高并发性和性能,特别是在读操作远多于写操作的情况下。Java
中的ReadWriteLock
接口及其主要实现类ReentrantReadWriteLock
提供了这种功能。
读写锁主要是为了解决读共享的问题。如果不使用读写锁且此时有很多读的请求,那么每次读请求也会进行加锁释放锁完成同步操作,降低了处理速度。而读写锁的读锁是共享锁,可以使得多个线程同时读取共享资源。
2. ReentrantReadWriteLock
实现类
ReentrantReadWriteLock
是ReadWriteLock
接口的主要实现类,它提供了可重入的读写锁。
主要特点
- 可重入:同一线程可以多次获取读锁或写锁。
- 公平和非公平模式:默认情况下,
ReentrantReadWriteLock
是非公平的,但可以通过构造函数指定为公平锁。 - 读锁共享:多个线程可以同时持有读锁。
- 写锁独占:写锁是独占的,一次只能由一个线程持有。
构造方法
ReentrantReadWriteLock()
: 创建一个非公平的读写锁。ReentrantReadWriteLock(boolean fair)
: 创建一个指定公平性的读写锁。
主要方法
readLock()
: 返回用于读操作的锁。writeLock()
: 返回用于写操作的锁。
3. 代码示例
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.lang.Thread.sleep;
public class Test {
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private static int value;
public static int readValue() {
readLock.lock();
try {
// 模拟读取操作
System.out.println(Thread.currentThread().getName() + "Start read value " );
sleep(1000L);
System.out.println(Thread.currentThread().getName() + "Read value: " + value);
return value;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
readLock.unlock();
}
}
public static void writeValue(int newValue) {
writeLock.lock();
try {
// 模拟写入操作
System.out.println(Thread.currentThread().getName() + "Start wrote new value" );
value = newValue;
sleep(1000L);
System.out.println(Thread.currentThread().getName() + "Wrote new value" + value);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
// 创建多个读线程
new Thread(()->{
for (int i = 0; i < 5; i++) {
readValue();
}
}).start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
readValue();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
int num = (int) (Math.random() * 100);
writeValue(num);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
int num = (int) (Math.random() * 100);
writeValue(num);
}
}).start();
}
}
可以看到,我们定义了一个变量value
用来模拟共享资源,且定义了两个函数用来模拟对该共享资源的读操作和写操作。在main
函数里,创建了读线程和写线程,对该共享资源进行访问。
结果:
Thread-0Start read value
Thread-1Start read value
Thread-0Read value: 0
Thread-1Read value: 0
Thread-2Start wrote new value
Thread-2Wrote new value50
Thread-2Start wrote new value
Thread-2Wrote new value7
Thread-2Start wrote new value
Thread-2Wrote new value78
Thread-2Start wrote new value
Thread-2Wrote new value6
Thread-2Start wrote new value
Thread-2Wrote new value91
Thread-3Start wrote new value
Thread-3Wrote new value32
Thread-3Start wrote new value
Thread-3Wrote new value94
Thread-3Start wrote new value
Thread-3Wrote new value90
Thread-3Start wrote new value
Thread-3Wrote new value84
Thread-3Start wrote new value
Thread-3Wrote new value3
Thread-0Start read value
Thread-1Start read value
Thread-1Read value: 3
Thread-1Start read value
Thread-0Read value: 3
Thread-0Start read value
Thread-1Read value: 3
Thread-1Start read value
Thread-0Read value: 3
Thread-0Start read value
Thread-1Read value: 3
Thread-1Start read value
Thread-0Read value: 3
Thread-0Start read value
Thread-1Read value: 3
Thread-0Read value: 3
从结果中可以看到,读操作可以同时进行,而写操作必须独占访问。
4. 锁降级
4.1 简单介绍
锁降级是指从写锁降级为读锁的过程,这是允许的,但锁升级(从读锁升级为写锁)通常是不允许的。锁降级通常是为了在修改共享资源后,保持一定的读访问,而不完全释放对资源的保护。其工作原理如下
- 持有写锁
一开始,线程需要持有写锁,以进行写操作。这是因为写操作需要独占访问共享资源,防止数据不一致 - 获取读锁
在持有写锁的同时,线程可以尝试获取读锁。由于线程已经持有写锁,因此获取读锁不会被阻塞。此时,线程同时持有写锁和读锁。 - 释放写锁
在获取读锁之后,线程可以释放写锁,此时线程仍然持有读锁。这样做的好处是,写操作已经完成,资源处于一致状态,线程仍然可以继续读取资源,同时允许其他读线程访问资源,但写线程必须等待。
4.2 代码示例
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.lang.Thread.sleep;
public class Test {
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private static int value;
public static void writeAndRead() {
writeLock.lock();
try {
// 模拟写操作
value++;
System.out.println("Write: " + value);
// 锁降级 - 在持有写锁的同时获取读锁
readLock.lock();
} finally {
// 释放写锁,但仍然持有读锁
writeLock.unlock();
}
try {
// 模拟读操作
System.out.println("Read after write: " + readValue());
} finally {
// 最终释放读锁
readLock.unlock();
}
}
public static int readValue() {
readLock.lock();
try {
// 模拟读取操作
return value;
} finally {
readLock.unlock();
}
}
public static void main(String[] args) {
Thread thread = new Thread(() -> {
writeAndRead();
});
thread.start();
}
}
工作流程:
- 获取写锁:
writeLock.lock()
用于开始写操作,确保当前线程对共享资源的独占访问。 - 执行写操作:修改共享资源,增加
value
。 - 获取读锁:在持有写锁的同时获取读锁,这确保了数据的一致性。
- 释放写锁:在获取读锁之后,释放写锁。此时,线程仍然持有读锁,可以继续读取资源。
- 执行读操作:在持有读锁的情况下读取资源。
- 释放读锁:完成读操作后,释放读锁。
通过锁降级,写线程在完成写操作后仍然可以继续读取资源,同时允许其他读线程并发地读取资源,提高了系统的并发性和性能。