读写锁:读读不互斥,读写互斥,写写互斥;
也就是说:
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; } }}
就先到这吧 内容的确不少 看的有点蒙
有问题能够留言哦,或者公众号留言(回复快):