话说 ReadWriteLock
ReadWriteLock
读写锁:读读不互斥,读写互斥,写写互斥;
也就是说:
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; } } }
就先到这吧 内容确实不少 看的有点蒙
欢迎关注公众号: