在说读写锁之前先来了解一下什么是独占锁和共享锁。
独占锁
指该锁一次只能被一个线程所持有,对 ReentrantLock 和 synchronized 而言都是独占锁。
共享锁
指该锁可被多个线程锁所持有。
对 ReentrantReadWriteLock 而言,其读锁是共享锁,写锁是独占锁。
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该是可以同时进行的。但是,如果有一个线程想去写共享资源,就不应该再有其他线程对该资源进行读或写。我们稍微做一下分析也就是如下结果:
读-读能共存
读-写不能共存
写-写不能共存
接下来我们实现一个简单的缓存功能,使用两种方式来实现。
- 不使用读写锁
public class BeforeReadWriteLockDemo {
public static void main(String[] args) {
BeforeCache cache = new BeforeCache();
// 写
for(int i = 0; i < 5; i++){
final int temp = i;
new Thread(() -> {
cache.set(temp + "", temp);
}, "t1").start();
}
// 读
for(int i = 0; i < 5; i++){
final int temp = i;
new Thread(() -> {
cache.get(temp + "");
}, "t2").start();
}
}
}
class BeforeCache {
private volatile Map<String, Object> cache = new HashMap<>();
public void set(String key, Object value) {
System.out.println(Thread.currentThread().getName() + " 正在写入:" + key);
cache.put(key, value);
try {
TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName() + " 写入 " + key + " 完成");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + " 正在读取:" + key);
Object result = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读取 " + result + " 完成");
}
}
我们来看一下输出结果:
t1 正在写入:0
t1 正在写入:1
t1 正在写入:2
t1 正在写入:3
t1 正在写入:4
t2 正在读取:0
t2 读取 0 完成
t2 正在读取:1
t2 正在读取:2
t2 读取 1 完成
t2 读取 2 完成
t2 正在读取:3
t2 读取 3 完成
t2 正在读取:4
t2 读取 4 完成
t1 写入 0 完成
t1 写入 1 完成
t1 写入 3 完成
t1 写入 4 完成
t1 写入 2 完成
通过观察上述输出结果,发现写操作中途会被插入其他的操作,我们的要求是线程在进行写的过程应该是单一的,也就是阻塞的,然而上述的操作好像并不是。其实使用之前我们说的 ReentrantLock 也是可以的,但是对于读的场景来说并不需要阻塞,我们只要求写操作是单一的即可,为了达到多线程环境下写操作独占锁和读操作共享锁,也就是出于性能考虑,我们使用 J.U.C 包中的读写锁 ReentrantReadWriteLock 来实现基于读写锁的缓存功能。看下述代码实现。
- 使用读写锁
public class AfterReadWriteLockDemo {
public static void main(String[] args) {
AfterCache cache = new AfterCache();
// 写
for(int i = 0; i < 5; i++){
final int temp = i;
new Thread(() -> {
cache.set(temp + "", temp);
}, "t1").start();
}
// 读
for(int i = 0; i < 5; i++){
final int temp = i;
new Thread(() -> {
cache.get(temp + "");
}, "t2").start();
}
}
}
class AfterCache {
private volatile Map<String, Object> cache = new HashMap<>();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void set(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在写入:" + key);
cache.put(key, value);
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName() + " 写入 " + key + " 完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在读取:" + key);
Object result = cache.get(key);
System.out.println(Thread.currentThread().getName() + " 读取 " + result + " 完成" );
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
运行结果:
t1 正在写入:0
t1 写入 0 完成
t1 正在写入:2
t1 写入 2 完成
t1 正在写入:1
t1 写入 1 完成
t1 正在写入:3
t1 写入 3 完成
t1 正在写入:4
t1 写入 4 完成
t2 正在读取:0
t2 读取 0 完成
t2 正在读取:1
t2 正在读取:3
t2 正在读取:2
t2 读取 2 完成
t2 正在读取:4
t2 读取 3 完成
t2 读取 1 完成
t2 读取 4 完成
通过观察上述的结果,确实跟我们预想的是一致的,这也就达到了 写独占与读共享 的目的。
网友评论