这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
一、前言
读写锁,解决了这样的问题:既可以保证多个线程同时读的效率,同时又可以保证有写入操作时的线程安全。
在读的地方合理使用读锁,在写的地方合理使用写锁,灵活控制,可以提高程序的执行效率。
Tips: 读本身是线程安全的,加读锁,主要是为了让写锁感知到,在有人读取的时候,不要同时写入。
读写锁的获取规则
在使用读写锁时遵守下面的获取规则:
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请读锁,可以申请成功。
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,因为读写不能同时操作。
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,都必须等待之前的线程释放写锁,同样也因为读写不能同时,并且两个线程不应该同时写。
所以用一句话总结:要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现。
总结:读读共享、其他都互斥(写写互斥、读写互斥、写读互斥)
二、使用案例
ReentrantReadWriteLock 是 ReadWriteLock 的实现类,最主要的有两个方法:
readLock():获取读锁writeLock():获取写锁
简单加写锁过程,如图:
举个栗子来应用读写锁:
public class ReadWriteLockDemo {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
false);
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(500);
} 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(500);
} 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()).start();
new Thread(() -> read()).start();
new Thread(() -> write()).start();
new Thread(() -> write()).start();
}
}
输出结果如下:
Thread-0得到读锁,正在读取
Thread-1得到读锁,正在读取
Thread-0释放读锁
Thread-1释放读锁
Thread-2得到写锁,正在写入
Thread-2释放写锁
Thread-3得到写锁,正在写入
Thread-3释放写锁
三、问题
ReentrantReadWriteLock源码
(1)写锁是如何基于 AQS 的 state 变量完成加锁的?
AQS 加锁,无非就是:
- 判断
state,更新state - 更新
AQS当前线程
RentrantReadWriteLock 只是在此基础上,增加了些东西。
首先了解下 RentrantReadWriteLock 中的实现 AQS 的 Sync:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 入参 c, 一般传入 state
// 1. 高 16 位:表示读锁数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 2. 低 16 位:表示写锁数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
}
举个栗子,其中尝试获取锁,源码如下:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
// 获取到 AQS 中 state = 0
int c = getState();
// state 二进制的值,高低位来代表读锁和写锁
// 高 16 位:读锁
// 低 16 位:写锁
// 获得 state 低 16 位,即写锁的状态
int w = exclusiveCount(c);
// c != 0, 表示有人加过锁了
if (c != 0) {
// 1. c != 0, w == 0 情况
// 1.1. w == 0, 所名有人加了读锁,没有人加写锁
// 1.2. 当前线程是否为之前加锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 2. c != 0, w != 0 情况: 之前有人加了写锁
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 可重入锁重点:
// 说明,是在可重入的加写锁,那么就 state += 1 即可。
setState(c + acquires);
return true;
}
// 非公平锁:这时会去尝试加锁
// 公平锁:此时会判断队列中有等待线程,就不加锁了
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
}
\