1.读写锁的使用和结构
在多线程访问同一个资源的情况下要保证线程安全性问题还要保证性能可以采用JDK提供的读写锁,读写锁更加适合在读远多于写的场景。读写锁的特点是:读读并发、读写互斥、写读互斥、写写互斥。在读大于写的并发场景,读写锁比ReentrantLock性能高很多。读写锁内部维护了2个锁,一个负责读锁,一个负责写锁。读锁在同一时刻可以由多个读线程持有。读锁是共享的,写锁是独占的。
2.读写锁设计
2.1 锁状态的记录
在AQS中通过state值来标识锁的状态,读写锁因为有两把锁,所以需要将state一个变量来表示两个锁的状态。通过int的高低位来表示,高16位表示读锁,每多一个线程持有高位就+1。低16位表示写锁,线程每重入一次,低16位+1。读锁的重入次数是通过ThreadLocal来记录的。
2.2 HoldCounter计数器
读锁的重入是依靠ThreadLocalHoldCounter类来和持有读锁的线程绑定,通过ThreadLocal来记录线程持有读锁的重入次数。
2.3 写锁加锁
2.4 读锁加锁
3.锁的降级
读写锁的锁降级指的是写锁降级为读锁,如果当前线程持有写锁,然后再获取读锁,然后释放写锁的过程。锁降级的目的是为了保证数据的可见性问题。如果读锁被多个线程持有,其中任意一个线程获取了写锁并更新了值那么这个更新后的值对其他读锁线程是不可见的。写锁 ->读锁 -> 释放写锁。
4.锁的饥饿
在读远大于写、高并发的场景下,读锁由于是共享的,大量线程的读请求是并发执行,但是无法有另外线程加写锁,写锁一直处于互斥状态,容易造成写锁的饥饿。