读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作。读锁可以由多个线程同时持有,又称共享锁。写锁同一时间只能由一个线程持有,又称互斥锁。同一时间,两把锁不能被不同线程持有。读写锁适合读取操作多于写入操作的场景,改进互斥锁的性能,比如集合的并发安全性改造,缓存组件等。
手动实现ReentrantReadWriteLock示例:
public class MyReadWriteLock {
private AtomicInteger readCount = new AtomicInteger(0);
private AtomicInteger writeCount = new AtomicInteger(0);
// 独占锁 拥有者 private AtomicReference<Thread> owner = new AtomicReference<>();
// 等待队列 private volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<WaitNode>();
class WaitNode {
int type = 0; // 0 为想获取独占锁的线程, 1为想获取共享锁的线程 Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int arg) {
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
// 获取独占锁 public void lockWrite() {
int arg = 1;
// 尝试获取独占锁,若成功,退出方法, 若失败...
if (!tryLockWrite(arg)) {
// 标记为独占锁 WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
waiters.offer(waitNode); // 进入等待队列 // 循环尝试拿锁 for (; ; ) {
// 若队列头部是当前线程 WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (!tryLockWrite(arg)) { // 再次尝试获取 独占锁 LockSupport.park(); // 若失败,挂起线程 } else { // 若成功获取 waiters.poll(); // 将当前线程从队列头部移除 return; // 并退出方法 }
} else { // 若不是队列头部元素 LockSupport.park(); // 将当前线程挂起 }
}
}
}
// 释放独占锁 public boolean unlockWrite() {
int arg = 1;
// 尝试释放独占锁 若失败返回true,若失败...
if (tryUnlockWrite(arg)) {
WaitNode next = waiters.peek(); // 取出队列头部的元素 if (next != null) {
Thread th = next.thread;
LockSupport.unpark(th); // 唤醒队列头部的线程 }
return true; // 返回true
}
return false;
}
// 尝试获取独占锁 public boolean tryLockWrite(int acquires) {
// 如果read count !=0 返回false
if (readCount.get() != 0) return false;
int wct = writeCount.get(); // 拿到 独占锁 当前状态 if (wct == 0) {
if (writeCount.compareAndSet(wct, wct + acquires)) { // 通过修改state来抢锁 owner.set(Thread.currentThread()); // 抢到锁后,直接修改owner为当前线程 return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); // 修改count值 return true;
}
return false;
}
// 尝试释放独占锁 public boolean tryUnlockWrite(int releases) {
// 若当前线程没有 持有独占锁 if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException(); // 抛IllegalMonitorStateException
}
int wc = writeCount.get();
int nextc = wc - releases; // 计算 独占锁剩余占用 writeCount.set(nextc); // 不管是否完全释放,都更新count值 if (nextc == 0) { // 是否完全释放 owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
// 获取共享锁 public void lockRead() {
int arg = 1;
if (tryLockRead(arg) < 0) { // 如果tryAcquireShare失败 // 将当前进程放入队列 WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
waiters.offer(node); // 加入队列 for (; ; ) {
// 若队列头部的元素是当前线程 WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (tryLockRead(arg) >= 0) { // 尝试获取共享锁, 若成功 waiters.poll(); // 将当前线程从队列中移除 WaitNode next = waiters.peek();
if (next != null && next.type == 1) { // 如果下一个线程也是等待共享锁 LockSupport.unpark(next.thread); // 将其唤醒 }
return; // 退出方法 } else { // 若尝试失败 LockSupport.park(); // 挂起线程 }
} else { // 若不是头部元素 LockSupport.park();
}
}
}
}
// 解锁共享锁 public boolean unLockRead() {
int arg = 1;
if (tryUnLockRead(arg)) { // 当read count变为0,才叫release share成功 WaitNode next = waiters.peek();
if (next != null) {
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
// 尝试获取共享锁 public int tryLockRead(int acquires) {
for (; ; ) {
if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) return -1;
int rct = readCount.get();
if (readCount.compareAndSet(rct, rct + acquires)) {
return 1;
}
}
}
// 尝试解锁共享锁 public boolean tryUnLockRead(int releases) {
for (; ; ) {
int rc = readCount.get();
int nextc = rc - releases;
if (readCount.compareAndSet(rc, nextc)) {
return nextc == 0;
}
}
}
}
锁降级指的是写锁降级为读锁,是指持有写锁的同时,再获取读锁,随后释放写锁的过程。 写锁是线程独占,读锁是线程共享,所以写锁降级为读锁可行,而读锁升级为写锁不可行。
代码示例:
class TeacherInfoCache {
static volatile boolean cacheValid;
static final ReadWriteLock rwl = new ReentrantReadWriteLock();
static Object get(String dataKey) {
Object data = null;
// 读取数据,加读锁 rwl.readLock().lock();
try {
if (cacheValid) {
data = Redis.data.get(dataKey);
} else {
// 通过加锁的方式去访问DB,加写锁 rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (!cacheValid) {
data = DataBase.queryUserInfo();
Redis.data.put(dataKey, data);
cacheValid = true;
}
} finally {
// 锁降级 rwl.readLock().lock();
rwl.writeLock().unlock();
}
}
return data;
} finally {
rwl.readLock().unlock();
}
}
}
class DataBase {
static String queryUserInfo() {
System.out.println("查询数据库。。。");
return "name:Kody,age:40,gender:true,";
}
}
class Redis {
static Map<String, Object> data = new HashMap<>();
}
本文分享自 Coding Diary 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!