文章目录
一、ReentrantReadWriteLock的作用是什么?
ReentrantReadWriteLock同样也是JUC包下面的一个并发工具类,它主要是用来给一个对象的读写操作上锁的。它对于读多写少的场景在性能上会更优于synchronized、ReentrantLock。
二、ReentrantReadWriteLock的读锁可重入,那么它是如何记录重录次数的?
ReentrantReadWriteLock的读锁是通过共享锁实现的,一般而言共享锁是无法重入的,因为共享锁的state属性在获取锁的时候只能减而不像独占锁那样可以进行累加。因而ReentrantReadWriteLock的读锁是通过另外一套逻辑实现的可重入的:第一次获取锁的线程会有独立的变量来存储该线程的重入次数,而非第一次进入的线程,ReentrantReadWriteLock会创建一个HoldCounter计数器来对该线程的冲入次数进行统计,并且会将之放入当前线程的ThreadLocal属性中来保证各个线程之间的统计独立。
三、ReentrantReadWriteLock如何实现通过一个int来分别给读写两种操作上锁的?
ReentrantReadWriteLock是利用了int类型在内存中占用32位的特性来实现的,它将前16位的高位作为读锁的记录位置,后16位低位作为写锁的记录位置,然后通过位运算来记录读写锁状态的变换。
这样的设计模式自然也有它的局限,一旦线程数量超过了65536(16位二进制表示的最大值)个的话上锁的操作就会失败
四、ReentrantReadWriteLock的底层是如何实现的?
ReentrantReadWriteLock中的读锁是通过共享锁的机制实现的,唯一的不同点就在于它可重入(原因之前有做解答);ReentrantReadWriteLock中的写锁是通过独占锁的方式实现的所以能保证写写之间的互斥;它优于ReentrantLock的地方就是读读操作没有互斥,所以读多写少的情况下ReentrantReadWriteLock的性能是更好的。
五、ReentrantReadWriteLock的锁降级是什么意思?为什么要使用它?
ReentrantReadWriteLock的锁降级其实就是写锁变为读锁的过程。这个过程其实是在写锁释放之前实现的,它保证了线程之间变量的可见性从而实现了线程安全。
数据写入主存中是需要一定的时间的,如果是自增操作的话,在未更新主存之前就读出主存中的数据进行自增并写入会造成脏读,导致线程安全问题,加上读锁是为了给刷新主存数据提供时间
六、ReentrantReadWriteLock的简单使用
public class ReadAndWriteLock {
private Map<String, Object> map = new HashMap<>();
private ReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private Lock r = reentrantReadWriteLock.readLock();
private Lock w = reentrantReadWriteLock.writeLock();
private Object getMap(String key) {
System.out.println("准备获取读锁~~~");
r.lock();
try {
System.out.println(Thread.currentThread().getName() + ":获取数据成功!时间点为:" + System.currentTimeMillis());
return map.get(key);
} finally {
r.unlock();
System.out.println("释放读锁~~~");
}
}
private void putMap(String key, Object obj) {
System.out.println("准备获取写锁~~~");
w.lock();
try {
map.put(key, obj);
System.out.println(Thread.currentThread().getName() + ":插入数据成功!时间点为:" + System.currentTimeMillis());
} finally {
w.unlock();
System.out.println("释放写锁~~~");
}
}
public static void main(String[] args) {
ReadAndWriteLock readAndWriteLock = new ReadAndWriteLock();
new Thread(() -> {
readAndWriteLock.putMap("xmx", "你好哈!");
}).start();
new Thread(() -> {
readAndWriteLock.getMap("xmn");
}).start();
}
}
上述代码中,两个线程之间的读写业务可替换组合,规律如下:
- 读读并行、读写互斥、写读互斥、写写互斥