9_读写锁
Posted root_zhb
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9_读写锁相关的知识,希望对你有一定的参考价值。
1、概念
JAVA 的并发包提供了读写锁 ReentrantReadWriteLock
它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁
- 读锁:针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁:当前操作没有完成之前,它会阻断其他写锁和读锁。 - 线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
- 线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
- 读写锁三个重要特征
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读
2、代码
代码讲解:对于 MyMap1 类中的 read 和 write 操作,分别表示读写操作。运行代码可以看到:对于每个写操作,不会出现插队的情况,对于读操作会出现插队的情况。如果去掉所加的读锁和写锁,均会插队。
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
//创建减法计数器对象
CountDownLatch latch = new CountDownLatch(6);
MyMap1 myMap = new MyMap1();
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.write(temp, UUID.randomUUID().toString().substring(0,5));
//减法计数器减 1
latch.countDown();
},"线程" + i).start();
}
// 此行代码以后的程序进入阻塞状态,直到减法计数器减到 0 为止,然后开始执行下面的代码
latch.await();
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.read(temp);
},"线程" + (i + 6)).start();
}
}
}
class MyMap1{
volatile Map<Integer,String> map = new HashMap<>();
//创建读写锁对象
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public Map<Integer, String> getMap() {
return map;
}
void read(Integer key){
//给读锁加锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取的key为:" + key);
String result = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取key的结果为:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
//给读锁解锁
readWriteLock.readLock().unlock();
}
}
void write(Integer key,String value){
//给写锁加锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "执行写操作,key为:" + key + ",value为:" + value);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "执行写操作完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
//给写锁解锁
readWriteLock.writeLock().unlock();
}
}
}
3、锁降级
对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
锁降级中读锁的获取是否必要呢?
答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。
目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了 数据,则其更新对其他获取到读锁的线程是不可见的。
以上是关于9_读写锁的主要内容,如果未能解决你的问题,请参考以下文章
java中ReentrantReadWriteLock读写锁的使用
深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock)