jdk 源码分析(9)java ReentrantReadWriteLock分析

来源:互联网 发布:苹果手机文档软件 编辑:程序博客网 时间:2024/05/18 12:00
前面的一个章节:jdk 源码分析(7)ReentrantLock结构
分析了reentrantlock,里面的所有进程都是去争一个state,ReentrantReadWriteLock将分为两种情况,
read/write,两种之间存在竞争关系,而且write里面竞争是你死我活,但是read则是共享社会。write 和前面的reentrantlock很类似,所以里面的规则也是一样。需要分为公平与不公平,也需要排队。
一个state 针对两种锁感觉不够用,所以代码中将state 分为了 两部分,一个是共享部分,一个是排除部分。sharedCount 占用高位,exclusiveCount占用低位,

  1. static final int SHARED_SHIFT = 16;
  2. static final int SHARED_UNIT = (1 << SHARED_SHIFT);
  3. static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
  4. static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
  5. /** Returns the number of shared holds represented in count */
  6. static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
  7. /** Returns the number of exclusive holds represented in count */
  8. static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }



ReentrantReadWriteLock有以下几个特性:

  • 公平性
    • 非公平锁(默认) 这个和独占锁的非公平性一样,由于读线程之间没有锁竞争,所以读操作没有公平性和非公平性,写操作时,由于写操作可能立即获取到锁,所以会推迟一个或多个读操作或者写操作。因此非公平锁的吞吐量要高于公平锁。
    • 公平锁 利用AQS的CLH队列,释放当前保持的锁(读锁或者写锁)时,优先为等待时间最长的那个写线程分配写入锁,当前前提是写线程的等待时间要比所有读线程的等待时间要长。同样一个线程持有写入锁或者有一个写线程已经在等待了,那么试图获取公平锁的(非重入)所有线程(包括读写线程)都将被阻塞,直到最先的写线程释放锁。如果读线程的等待时间比写线程的等待时间还有长,那么一旦上一个写线程释放锁,这一组读线程将获取锁。
  • 重入性
    • 读写锁允许读线程和写线程按照请求锁的顺序重新获取读取锁或者写入锁。当然了只有写线程释放了锁,读线程才能获取重入锁。
    • 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁。
    • 另外读写锁最多支持65535个递归写入锁和65535个递归读取锁。
  • 锁降级
    • 写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。
  • 锁升级
    • 读取锁是不能直接升级为写入锁的。因为获取一个写入锁需要释放所有读取锁,所以如果有两个读取锁视图获取写入锁而都不释放读取锁时就会发生死锁。
  • 锁获取中断
    • 读取锁和写入锁都支持获取锁期间被中断。这个和独占锁一致。
  • 条件变量
    • 写入锁提供了条件变量(Condition)的支持,这个和独占锁一致,但是读取锁却不允许获取条件变量,将得到一个UnsupportedOperationException异常。
  • 重入数
    • 读取锁和写入锁的数量最大分别只能是65535(包括重入数)。


基于上面的规则,定义两种锁
public interface ReadWriteLock {    Lock readLock();    Lock writeLock();}
写锁:尝试去获取一下锁,如果没获得就加入队列,然后在队列里等。
  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. selfInterrupt();
  5. }
尝试去获取写锁,首先会判断是否已经有写锁了,如果有就跳过,加入队列,等机会,如果没有,还需要判断是否有读锁,如果读锁是自己的,那么也可以放过,进入写锁。
  1. protected final boolean tryAcquire(int acquires) {
  2. Thread current = Thread.currentThread();
  3. int c = getState();
  4. int w = exclusiveCount(c);
  5.              //有锁
  6. if (c != 0) {
  7.                 //但是不是写锁,而且当前读锁并不是自己的。你只能回去等
  8. if (w == 0 || current != getExclusiveOwnerThread())
  9. return false;
  10. if (w + exclusiveCount(acquires) > MAX_COUNT)
  11. throw new Error("Maximum lock count exceeded");
  12. // Reentrant acquire
  13. setState(c + acquires);
  14. return true;
  15. }
  16. if (writerShouldBlock() ||
  17. !compareAndSetState(c, c + acquires))
  18. return false;
  19. setExclusiveOwnerThread(current);
  20. return true;
  21. }


但是readlock,就很简单,没有加入队列,应为read之间不存在竞争关系。申请的共享锁。
  1. public final void acquireShared(int arg) {
  2. if (tryAcquireShared(arg) < 0)
  3. doAcquireShared(arg);
  4. }

读锁的调用:获取当前状态state ,判断是否存在排除锁(写锁)
  1. protected final int tryAcquireShared(int unused) {
  2. Thread current = Thread.currentThread();
  3. //获取当前状态state
  4. int c = getState();
  5.             //判断是否存在排除锁
  6. if (exclusiveCount(c) != 0 &&
  7. getExclusiveOwnerThread() != current)
  8. return -1;
  9.             //获取读锁个数
  10. int r = sharedCount(c);
  11. if (!readerShouldBlock() &&
  12. r < MAX_COUNT &&
  13.                 //增加读锁
  14. compareAndSetState(c, c + SHARED_UNIT)) {
  15. if (r == 0) {
  16. firstReader = current;
  17. firstReaderHoldCount = 1;
  18. } else if (firstReader == current) {
  19. firstReaderHoldCount++;
  20. } else {
  21. HoldCounter rh = cachedHoldCounter;
  22. if (rh == null || rh.tid != getThreadId(current))
  23. cachedHoldCounter = rh = readHolds.get();
  24. else if (rh.count == 0)
  25. readHolds.set(rh);
  26. rh.count++;
  27. }
  28. return 1;
  29. }
  30. return fullTryAcquireShared(current);
  31. }

总结:
写锁会去判断是否有写锁,如果没有,在判断是否有读锁,如果有读锁,在判断是否是自己的,
读锁只需要判断是否有写锁

锁的释放将不分析了。

阅读全文
0 0
原创粉丝点击