话说 ReadWriteLock
发布于: 2021 年 04 月 05 日
读写锁:读读不互斥,读写互斥,写写互斥;
也就是说:
A 读的时候 B 可以读,
A 读的时候 B 不可以写,
A 写的时候 B 不可以写
这里举个例子:不同线程对变量 x 读 写
public class ReadWriteLockTest {
ReadWriteLock rw = new ReentrantReadWriteLock();
public int x = 0;
public static void main(String[] args) {
}
// A读
public void A(){
try{
// 读锁
rw.readLock().lock();
System.out.println("A开始读: x="+x);
sleep(5);
System.out.println("A读完了: x="+x);
} catch (Exception e) {
e.printStackTrace();
}finally {
rw.readLock().unlock();
}
}
// B读
public void B(){
try{
// 读锁
rw.readLock().lock();
System.out.println("B开始读: x="+x);
sleep(5);
System.out.println("B读完了: x="+x);
} catch (Exception e) {
e.printStackTrace();
}finally {
rw.readLock().unlock();
}
}
// C写
public void C(){
try{
// 写锁
rw.writeLock().lock();
System.out.println("C开始写: x="+x);
sleep(5);
x = 10;
System.out.println("C写完了: x="+x);
} catch (Exception e) {
e.printStackTrace();
}finally {
rw.writeLock().unlock();
}
}
// D写
public void D(){
try{
// 写锁
rw.writeLock().lock();
System.out.println("D开始写: x="+x);
sleep(5);
x = 100;
System.out.println("D写完了: x="+x);
} catch (Exception e) {
e.printStackTrace();
}finally {
rw.writeLock().unlock();
}
}
// E 同一线程 读写不互斥
public void E(){
try{
// 写锁
rw.writeLock().lock();
System.out.println("E开始写: x="+x);
x = 99;
rw.readLock().lock();
System.out.println("E没写完呢 E开始读:x="+x);
x = 100;
System.out.println("E写完了: x="+x);
} catch (Exception e) {
e.printStackTrace();
}finally {
rw.writeLock().unlock();
}
}
// 睡眠指定秒
public void sleep(int s){
try {
Thread.sleep(s*1000);
} catch (Exception e){
e.printStackTrace();
}
}
}
复制代码
1. A 读 B 可读 读读共享
public static void main(String[] args) {
ReadWriteLockTest test = new ReadWriteLockTest();
new Thread(test::A).start();
new Thread(test::B).start();
}
输出结果:
A开始读: x=0
B开始读: x=0
B读完了: x=0
A读完了: x=0
复制代码
2. A 读 C 不可写 读写互斥
public static void main(String[] args) {
ReadWriteLockTest test = new ReadWriteLockTest();
new Thread(test::A).start();
new Thread(test::C).start();
}
输出结果:
A开始读: x=0
A读完了: x=0
C开始写: x=0
C写完了: x=10
复制代码
3. B 写 A 不可读 读写互斥
public static void main(String[] args) {
ReadWriteLockTest test = new ReadWriteLockTest();
new Thread(test::C).start();
new Thread(test::A).start();
}
输出结果:
C开始写: x=0
C写完了: x=10
A开始读: x=10
A读完了: x=10
复制代码
4. C 写 D 不可写 写写互斥
public static void main(String[] args) {
ReadWriteLockTest test = new ReadWriteLockTest();
new Thread(test::C).start();
new Thread(test::D).start();
}
输出结果:
C开始写: x=0
C写完了: x=10
D开始写: x=10
D写完了: x=100
复制代码
5. 线程自己读写不互斥
public static void main(String[] args) {
ReadWriteLockTest test = new ReadWriteLockTest();
new Thread(test::E).start();
}
复制代码
6. 总结
读读共享,读写互斥,写写互斥
可以把读比作是女生,把共享资源比作是厕所,女生跟女生可以拉手进厕所(读读),女生和男生不可以拉手进厕所(读写),男生和男生不可以拉手进厕所(写写)
7. 唠一唠实现方式
7.1 类继承关系
7.2 lock 过程
ReadWriteLock rw = new ReentrantReadWriteLock();
rw.readLock().lock();
复制代码
读锁 lock 大体流程是这样的:
与 ReentrantLock 获取锁的过程基本一致,只是在 tryAcquire(写锁) 与 tryReleaseShared(读锁) 的时候有些区别
前置知识:
读写锁是怎么用 state 标记是读锁(数量)还是写锁(数量)的 ,要是我设计这个代码,
我可能会用 int readState,int writeState , 两个单独的状态来标识读锁 (数量)和写锁(数量)
但是 AQS 说了 只能用 一个 state 和 一个双向队列 来 实现 (模板方法),你不能自己瞎给我改。
看看大佬们怎么实现的:
大佬把 state 切开了,int 类型数据大小为 4 字节 32 位 ,大佬把 32 分成了 16+16 高 16 位表示共享锁,低 16 位表示独占锁
tryAcquireShared
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取state
int c = getState();
// 看一下是不是独占状态(写锁),如果是独占再看一下持有锁的线程是不是当前线程,如果不是返回-1 失败
// 如果线程E获取了独占锁 他是可以再获取共享锁锁的 看5线程自己读写不互斥例子
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取共享锁数量(高16位)
int r = sharedCount(c);
// 判断是不是需要block
if (!readerShouldBlock() &&
// 判断获取锁的线程有没有超过最大线程
r < MAX_COUNT &&
// cas设置state 这里不是c+1 是c+SHARED_UNIT
// SHARED_UNIT的二进制位: 10000_0000_0000_0000
// 为什么是加这个 因为是高16位+1 也就是需要加65536=65535+1
compareAndSetState(c, c + SHARED_UNIT)) {
// 到这里其实已经获取锁成功了 下边的一些操作 是设置一些需要的属性
if (r == 0) {
// 如果是第一个独占锁 就设置firstReader为当前线程
// firstReaderHoldCount = 1
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 如果第一个独享锁占有者是自己 那就firstReaderHoldCount++
firstReaderHoldCount++;
} else {
// 第一个独占锁不是自己 这里操作骚里骚气 没有很懂
// 只知道是把持有独占锁的次数+1(排除第一个获取独占锁的线程 因为上边那两个变量单独记录了)
// 这里用到了threadlocal技术
// cachedHoldCounter 这个玩意存着最后获取共享锁的线程 和 数量
HoldCounter rh = cachedHoldCounter;
//
if (rh == null || rh.tid != getThreadId(current))
// readHolds.get()就是返回ThreadLocal中存储的对象 线程第一次进来会创建
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 独占锁总数+1
rh.count++;
}
return 1;
}
// 如果没有成功 调用fullTryAcquireShared
return fullTryAcquireShared(current);
}
复制代码
fullTryAcquireShared
// 上边失败了 这里就死循环获取锁
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
// 获取state
int c = getState();
// 跟上边一样
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
// readerShouldBlock这个公平锁和非公平锁的逻辑不一样
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
// 最大获取锁线程数校验
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// +1
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 上边一样 一系列设置
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
复制代码
就先到这吧 内容确实不少 看的有点蒙
有问题可以公=众=号=留言:
划线
评论
复制
发布于: 2021 年 04 月 05 日阅读数: 9
木子的昼夜
关注
还未添加个人签名 2018.03.28 加入
还未添加个人简介
评论