什么是读写锁
- 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
- 读锁是共享锁,写锁是独占锁。一个资源可以被多个读线程访问,也可以被一个写线程访问,但是不能同时存在读写线程对资源的访问;读写互斥、读读共享。
- 针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
而读写锁有以下三个重要的特性: - 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
读写锁演变
读写锁降级
入门案例
使用 ReentrantReadWriteLock 对一个 hashmap 进行读和写操作
package com.itguigu.JUC.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author 小杰 @Date 2022/5/7 20:49 @Version 1.0 读写锁入门案例: 使用 ReentrantReadWriteLock 对一个 hashmap
* 进行读和写操作
*/
public class ReadWriteLock {
public static void main(String[] args) {
Mycache mycache = new Mycache();
// 创建5线程进行写操作
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(
() -> {
mycache.put("" + num, "" + num);
},
String.valueOf(i))
.start();
}
// 创建5线程进性读操作。
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(
() -> {
mycache.read("" + num);
},
String.valueOf(i))
.start();
}
}
}
/** 资源类:map和读写操作。 */
class Mycache {
Map<String, Object> map = new HashMap<>();
// 创建读写锁对象
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 写操作
*
* @param key
* @param value
*/
public void put(String key, Object value) {
// 添加写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在写操作...");
map.put(key, value);
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "写完了...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
readWriteLock.writeLock().unlock();
}
}
/**
* 读操作
*
* @param key
* @return
*/
public Object read(String key) {
//上锁
readWriteLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName() + "正在读操作---");
result = map.get(key);
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "读取完了---");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//释放锁
readWriteLock.readLock().unlock();
}
return result;
}
}
结果
小结
- 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。 • 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
- 原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。