ReentrantReadWriteLock:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。
读锁是共享锁,可以有多个线程读;而写锁是独占锁,同时只可能有一个线程写。
共享变量
读锁可以多线程访问,写锁只可以有一个线程访问,我们很容易想到可以使用两个变量来表示读写状态。但是,AQS却只是使用一个state来实现。
1 | static final int SHARED_SHIFT = 16; |
举个例子来看:
这里有两个关键方法sharedCount和exclusiveCount,通过名字可以看出sharedCount是共享锁的数量,exclusiveCount是独占锁的数量。
共享锁通过对c像右位移16位获得,独占锁通过和16位的1与运算获得。
state前十六位代表读锁,后十六位代表写锁。
举个例子,当获取读锁的线程有3个,写锁的线程有1个(当然这是不可能同时有的),state就表示为0000 0000 0000 0011 0000 0000 0000 0001,高16位代表读锁,通过向右位移16位(c >>> SHARED_SHIFT)得倒10进制的3,通过和0000 0000 0000 0000 1111 1111 1111 1111与运算(c & EXCLUSIVE_MASK),获得10进制的1。
由于16位最大全1表示为65535,所以读锁和写锁最多可以获取65535个。
WriteLock
写锁是一把独占锁,同时只可能有一个线程访问,而且不可能与读锁同时存在,所以与ReentrantLock不同的是,WriteLock不仅要判断是否还有其它写线程占用,还要考虑是否还有读线程占用。
读锁是否存在。因为要确保写锁的操作对读锁是可见的。如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。
1 | protected final boolean tryAcquire(int acquires) { |
锁降级
在获取写锁的时候,如果资源存在读锁,因为可能存在多个不同的线程读,要是修改了线程除了本线程别的线程也感知不到,那么肯定是无法获取写锁的。
但是,在获取读锁的时候, 如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁(锁降级),主要原因是因为在同一个线程内,写锁所做的修改读锁时立即可见的,但是在别的线程内就没有可见性了。
1 | class CachedData { |
ReadLock
- 申请读锁,资源上没有写锁,且读锁数量小于最大值,申请读锁成功。
- 申请读锁,资源上有写锁,且写锁就在本线程上,那么申请成功。