【Java8源码分析】locks包-ReentrantReadWriteLock

转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72854924


1 ReadWriteLock的使用

下面截取一段官网上的使用说明例子

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
       // Must release read lock before acquiring write lock
       rwl.readLock().unlock();
       rwl.writeLock().lock();
       try {
         // Recheck state because another thread might have
         // acquired write lock and changed state before we did.
         if (!cacheValid) {
           data = ...
           cacheValid = true;
         }
         // Downgrade by acquiring read lock before releasing write lock
         rwl.readLock().lock();
       } finally {
         rwl.writeLock().unlock(); // Unlock write, still hold read
       }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

2 辅助子类

ReetrantReadWriteLock在内部也是使用了AQS类来实现的,不了解AQS类的可以参考:AbstractQueuedSynchronizer同步器一文。

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    // 用一个int的state来表示状态,其中低16位表示持有写锁线程数,高16位表示读锁的
    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    // 以下位计算持有读、写锁线程的数量
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    // 每个线程读锁持有数
    static final class HoldCounter {
        int count = 0;
        final long tid = getThreadId(Thread.currentThread());
    }

    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }

    private transient ThreadLocalHoldCounter readHolds;

    // 以下三个变量主要减少readHolds的get次数
    // 上一个成功获取读锁线程的缓存值
    private transient HoldCounter cachedHoldCounter;
    // 第一个获取读锁的线程
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }

    abstract boolean readerShouldBlock();
    abstract boolean writerShouldBlock();

    protected final boolean tryRelease(int releases) {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        // release后的状态
        int nextc = getState() - releases;
        boolean free = exclusiveCount(nextc) == 0;
        // 如果持有写锁线程为0
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        // 持有读锁或写锁
        if (c != 0) {
            // 需排队区获取锁
            if (w == 0 || current != getExclusiveOwnerThread())
                return false;
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // Reentrant acquire
            setState(c + acquires);
            return true;
        }
        // 如果无线程获得锁,尝试获取锁
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }

    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        // 判断是否为第一个获得读锁的线程
        if (firstReader == current) {
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;
        } else {
            // 将当前线程所持有的锁的数量信息得到
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            // 然后将数量减少1
            if (count <= 1) {
                readHolds.remove();
                // 如果持有数为1,则直接将该线程变量移除
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        for (;;) {
            int c = getState();
            // 读锁数量减1
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        // 有写锁,且持有线程非本线程
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return -1;
        int r = sharedCount(c);
        // 如果可以获得读锁,且成功更新状态
        if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        // 更新读锁状态失败,或者本线程已经占有写锁
        return fullTryAcquireShared(current);
    }
}

3 流程图

在第2部分展示了同步器的几个主要方法,ReentrantReadWriteLock的所有源码跳转比较多,本文采用表格的方式说明,现在只需知道两点:

(1)在Sync内部有一个state的状态,记录了获取了read和write锁的数量,同时有一个ThreadLocal对象,记录了每个线程对read锁的重入数。

(2)对于获取锁失败的线程,不管是申请read锁的,还是write锁的,都会被包装成Node进入排队。

读锁获取

  1. 如果没有写锁被占用,成功获得锁
  2. 如果其他线程持有写锁,则转入排队队列
  3. 如果本线程持有写锁,且在排队队列中,该写锁的下一个节点不为空,且是申请写锁,则转入排队队列
  4. 如果本线程持有写锁,且在排队队列中,该写锁的下一个节点为空,则成功获得锁(锁降级)

写锁获取

  1. 有读锁,则转入排队队列
  2. 有写锁,且持有写锁的非本线程,则转入排队队列
  3. 有写锁,持有写锁的为该线程,修改锁的额数量,获取锁

PS:在排队队列中,不管是读锁,还是写锁,均按FIFO顺序获取锁


4 总结

  • 公平性

    • 非公平锁(默认) 这个和独占锁的非公平性一样,由于读线程之间没有锁竞争,所以读操作没有公平性和非公平性,写操作时,由于写操作可能立即获取到锁,所以会推迟一个或多个读操作或者写操作。因此非公平锁的吞吐量要高于公平锁
    • 公平锁 利用AQS的CLH队列,释放当前保持的锁(读锁或者写锁)时,优先为等待时间最长的那个写线程分配写入锁,当前前提是写线程的等待时间要比所有读线程的等待时间要长。同样一个线程持有写入锁或者有一个写线程已经在等待了,那么试图获取公平锁的(非重入)所有线程(包括读写线程)都将被阻塞,直到最先的写线程释放锁。如果读线程的等待时间比写线程的等待时间还有长,那么一旦上一个写线程释放锁,这一组读线程将获取锁
  • 重入性

    • 读写锁允许读线程和写线程按照请求锁的顺序重新获取读取锁或者写入锁。当然了只有写线程释放了锁,读线程才能获取重入锁
    • 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁
    • 另外读写锁最多支持65535个递归写入锁和65535个递归读取锁
  • 锁降级

    • 写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性
  • 锁升级

    • 读取锁是不能直接升级为写入锁的。因为获取一个写入锁需要释放所有读取锁,所以如果有两个读取锁视图获取写入锁而都不释放读取锁时就会发生死锁
  • 锁获取中断

    • 读取锁和写入锁都支持获取锁期间被中断。这个和独占锁一致。
  • 条件变量

    • 写入锁提供了条件变量(Condition)的支持,这个和独占锁一致,但是读取锁却不允许获取条件变量,将得到一个UnsupportedOperationException异常
  • 重入数

    • 读取锁和写入锁的数量最大分别只能是65535(包括重入数)

转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72854924

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值