背景
ReentrantReadWriteLock可以设置公平或非公平,为什么?
读锁插队策略
每次获取响应锁之前都要检查能否获取
- readerShouldBlock
- writerShouldBlock
公平锁
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
由此可见只要等待队列中有线程等待,也就是hasQueuedPredecessors返回true时,writer和reader都会block,乖乖排队
非公平锁
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
- 写锁始终是false,由此可见想要获取写锁随时可以插队
-
读锁
T1,T2正在持有读锁,T3请求写锁,于是进入等待,这时候T4插队获取读锁,允许T4插队还是不插队呢?
1.允许
少量线程插队确实可以提升效率,但是:
问题:如果读取线程不断增加,线程5,6,7,8,9都来请求,读锁长时间不释放,线程3进入饥饿状态,我写操作还执不执行了?
2.不允许
都有机会运行,都不会等待太久时间
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadLockJumpQueue {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到读锁,正在读");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到写锁,正在写");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> read(), "Thread-2").start();
new Thread(() -> read(), "Thread-4").start();
new Thread(() -> write(), "Thread-3").start();
new Thread(() -> read(), "Thread-5").start();
}
}
锁的升降级
读写锁的降级
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
//首先获取读锁
rwl.readLock().lock();
//判断缓存是否有效,有效直接输出
if (!cacheValid) {
//无效就把读锁释放,上写锁
//这个时候有可能有其他线程抢先获取了写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
//如果缓存还是无效
if (!cacheValid) {
//更新data,缓存设为有效
data = new Object();
cacheValid = true;
}
//因为我们想要在后续打印出data,所以请求读锁
rwl.readLock().lock();
} finally {
//确保写锁释放,整个时候就是读锁了,然后打印data
rwl.writeLock().unlock();
}
}
try {
System.out.println(data);
} finally {
//确保读锁能释放
rwl.readLock().unlock();
}
}
}
为什么需要锁降级?
如果我们一直使用写锁最后释放写锁,虽然线程安全,但是没必要,因为我们只有一处修改数据的代码:
后面data只是读,这个时候还用写锁就不能多个线程读了,持有写锁浪费资源,降低效率,这个时候就用锁的降级提高整体效率
ReentrantReadWriteLock支持锁降级,不支持锁升级
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CachedData {
final static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
upgrade();
}
public static void upgrade() {
//获取读锁
rwl.readLock().lock();
//获取写锁,发生阻塞
rwl.writeLock().lock();
//不会打印
System.out.println("成功升级");
}
}
rwl.writeLock().lock();
rwl.readLock().lock();
rwl.readLock().unlock();
System.out.println("成功降级");
由此可见ReentrantReadWriteLock支持锁降级,不支持锁升级
为什么不支持?
写锁,只能有一个线程持有,并且不可能存在读锁和写锁同时持有的情况
不然A升级,B升级,大家一起升级,但是写锁只有一个,给谁?
那只能这样,比如A,B,C都持有读锁,A尝试升级到写锁,那么B,C就必须释放读锁,这个时候A可以升级为写锁
那如果A,B都想升级为写锁,A等待其他线程释放,B等待其他线程释放,A等B,B等A,死锁
实现方案
保证每次只有一个线程升级那么线程是安全的,只不过最常见的ReentrantReadWriteLock不支持