这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
介绍
-
只要读读可以共存(允许多个线程同时读),其他情况都会有线程安全问题。
-
如果有一个线程想去写共享资源,就不应该在有其他线程可以对该资源进行读或写
-
提供了写锁和读锁两种锁的操作机制
ReentranReadWriteLock
ReadWriteLock提供了readLock和writeLock两种锁的操作机制,一个是读锁,一个是写锁,而它的实现类就是ReentranReadWriteLock
代码Demo
- 一个资源类MyCache,借用HashMap有put和get两个操作
- 模拟5个线程读,5个线程写的场景
- 在读写前加入延迟时间,便于观察是否有其他线程插入的场景
package com.lemon.lesson6_lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public class WriteReadLockDemo {
private ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> myCache.put(temp, temp), "write-" + temp).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp);
}, "read-" + temp).start();
}
}
}
class MyCache {
private Map<Object, Object> map = new HashMap<>();
public void put(Object key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t开始写");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
}
public Object get(Object key) {
System.out.println(Thread.currentThread().getName() + "\t开始读取");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object res = map.get(key);
System.out.println("读取完成,读取结果为:" + res);
return res;
}
}
运行结果:
write-0 开始写
write-3 开始写
write-2 开始写
write-4 开始写
write-1 开始写
read-1 开始读取
read-2 开始读取
read-0 开始读取
read-3 开始读取
read-4 开始读取
write-0 写入完成
write-4 写入完成
write-2 写入完成
write-3 写入完成
write-1 写入完成
读取完成,读取结果为:3
读取完成,读取结果为:2
读取完成,读取结果为:0
读取完成,读取结果为:null
读取完成,读取结果为:null
可以看到由于在某一个线程写入的过程中被其他线程打断,所以数据出现问题
解决方案:当然可以使用synchronized或者ReentrantLock,当时这样虽然解决了线程安全的问题,但是降低了并发性(实际上在是可以允许多个线程同时读的),所以我们引入了 java.util.concurrent.locks.ReentrantReadWriteLock
修改后的资源类MyCache
- 为了保证资源map的可见性和防止指令重排,使用volatile修饰
- 对于put操作使用写锁
- 对于get操作使用读锁
class MyCache {
private volatile Map<Object, Object> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(Object key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t开始写");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public Object get(Object key) {
Object res = null;
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t开始读取");
Thread.sleep(100);
res = map.get(key);
System.out.println("读取完成,读取结果为:" + res);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return res;
}
}
运行结果
write-0 开始写
write-0 写入完成
write-1 开始写
write-1 写入完成
write-2 开始写
write-2 写入完成
write-3 开始写
write-3 写入完成
write-4 开始写
write-4 写入完成
read-1 开始读取
read-2 开始读取
read-0 开始读取
read-4 开始读取
read-3 开始读取
读取完成,读取结果为:1
读取完成,读取结果为:4
读取完成,读取结果为:0
读取完成,读取结果为:2
读取完成,读取结果为:3
适用场景
可以看到相比于 ReentrantLock 适用于一般场合,ReentrantReadWriteLock对于写相关的操作并没有比ReentrantLock表现的优秀,但是对于共享读的场景,进一步提高了并发性。所以说ReadWriteLock 适用读多写少的场景,合理使用可以进一步提高并发