读写锁
1. 读写锁介绍
1.1 读写锁的演进
1.2 读写锁场景
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。
1.3 读写锁条件和特点
- 线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
- 线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
总结:一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享。
而读写锁有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
2. ReadWriteLock接口
java
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReadWriteLock接口提供了两个方法:一个用来获取读锁,一个用来获取写锁。ReentrantReadWriteLock实现了ReadWriteLock接口。
java
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Demo_ReentrantReadWriteLock test = new Demo_ReentrantReadWriteLock();
new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
}
public void get(Thread thread) {
rwl.readLock().lock();
try{
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
}finally {
rwl.readLock().unlock();
}
}
运行结果: 从运行结果看出使用读写锁可以让多个线程同时进行读操作, 这样就大大提升了读操作的效率。
提示
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
3. 锁降级
锁降级可以保证数据的一致性:通过先获取写锁再降级为读锁,可以确保数据在修改期间不被其他线程读取,保证了数据的一致性。
锁降级可以提高性能:在一些场景下,持有写锁期间需要多次读取数据比较耗时,可以先获取写锁,然后降级为读锁,通过锁降级可以减少锁的竞争,提高系统的并发性能。
java
public static void main(String[] args) throws ExecutionException, InterruptedException {
ReentrantReadWriteLock rrl = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rrl.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rrl.writeLock();
FutureTask<String> futureTask = new FutureTask<>(() -> {
// 获取写锁
writeLock.lock();
System.out.println("获取写锁===================");
// 继续获取读锁, 写锁本身所在线程可以获取读锁
readLock.lock();
System.out.println("读取数据===================");
// 锁降级,释放写锁
writeLock.unlock();
readLock.unlock();
return "success";
});
new Thread(futureTask, "demo").start();
futureTask.get();
}
运行结果: