写点什么

话说 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=0B开始读: x=0B读完了: x=0A读完了: 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=0A读完了: x=0C开始写: x=0C写完了: 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=0C写完了: x=10A开始读: x=10A读完了: 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=0C写完了: x=10D开始写: x=10D写完了: 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;        }    }}
复制代码



就先到这吧 内容确实不少 看的有点蒙


有问题可以公=众=号=留言:


用户头像

还未添加个人签名 2018.03.28 加入

还未添加个人简介

评论

发布
暂无评论
话说  ReadWriteLock