1. 前言
Java 中的读写锁实现是 ReentrantReadWriteLock , 是一种锁分离策略. 能有效提高读比写多的场景下的程序性能.
关于如何使用参见 并发编程之 Java 三把锁 http://thinkinjava.cn/article/36 .
由于读写锁较为复杂, 故分为篇文章进行源码分析, 今天先说较为简单的写锁.
2. 写锁介绍
不论是读锁还是写锁, 都是基于 AQS 的, 而 AQS 留给子类实现的就是 tryAcquire 或者 tryAcquireShared 方法, 前者是写锁的实现, 后者是读锁的实现, 从名字上可以看出, 一个是独占锁, 一个是共享锁.
今天看的就是 tryAcquire 和 tryRelease 的实现, 获取和释放.
3. tryAcquire 实现
源码加注释如下:
- // 如果有读锁, 此时是获取不到写锁的. 当有写锁时, 判断重入次数.
- // 当写锁空闲, 读锁空闲, 公平模式下, 如果队列中有等待的, 不会抢锁. 非公平模式下, 必抢锁.
- protected final boolean tryAcquire(int acquires) {
- // 写
- Thread current = Thread.currentThread();
- int c = getState();
- // 用 state & 65535 得到低 16 位的值.
- int w = exclusiveCount(c);
- if (c != 0) {
- // (Note: if c != 0 and w == 0 then shared count != 0)
- // 如果 state 不是 0, 且低 16 位是 0, 说明了什么? 说明写锁是空闲的, 读锁被霸占了. 那么也不能拿锁, 返回 fasle.
- // 如果低 16 位不是 0, 说明写锁被霸占了, 并且, 如果持有锁的不是当前线程, 那么这次拿锁是失败的. 返回 fasle.
- // 总之, 当有读锁, 就不能获取写锁. 当有写锁, 就必须是重入锁.
- if (w == 0 || current != getExclusiveOwnerThread())
- return false;
- // 到这一步了, 只会是写重入锁. 如果写重入次数超过最大值 65535, 就会溢出.
- if (w + exclusiveCount(acquires)> MAX_COUNT)
- throw new Error("Maximum lock count exceeded");
- // Reentrant acquire
- // 将 state + 1
- setState(c + acquires);
- return true;
- }
- // 当 state 是 0 的时候, 那么就可以获取锁了.
- // writerShouldBlock 判断是否需要锁. 非公平情况下, 返回 false. 公平情况下, 根据 hasQueuedPredecessors 结果判断.
- // 当队列中有锁等待了, 就返回 false 了.
- // 当是非公平锁的时候, 或者队列中没有等待节点的时候, 尝试用 CAS 修改 state.
- if (writerShouldBlock() ||
- !compareAndSetState(c, c + acquires))
- return false;
- // 修改成功 state 后, 修改锁的持有线程.
- setExclusiveOwnerThread(current);
详解的逻辑都写在注释里了, 可以对照源码查看, 这里再次总结这个方法的逻辑.
首先判断锁是否空闲.
如果空闲, 则根据公平与否判断是否应该获取锁, 当 writerShouldBlock 返回结果是 false 的时候, 就使用 CAS 修改 state 变量, 获取锁. 成功之后修改 AQS 持有线程.
如果不是空闲的, 则判断写锁是否是空闲的, 这里有 2 种情况:
3.1 如果写锁空闲, 但 state 不是 0, 说明有读锁, 那么就不能获取锁.
3.2 如果写锁不空闲, 判断持有 AQS 锁的线程是不是当前线程, 如果不是, 不能获取, 反之, 可以获取重入锁.
4. tryRelease 实现
代码加注释如下:
- protected final boolean tryRelease(int releases) {
- // 是否持有当前锁
- if (!isHeldExclusively())
- throw new IllegalMonitorStateException();
- // 计算 state 值
- int nextc = getState() - releases;
- // 计算写锁的状态, 如果是 0, 说明是否成功.
- boolean free = exclusiveCount(nextc) == 0;
- // 释放成功, 设置持有锁的线程为 null.
- if (free)
- setExclusiveOwnerThread(null);
- // 设置 state
- setState(nextc);
- return free;
- }
这里还是很简单的, 只是有一个地方需要注意:
- // 计算写锁的状态, 如果是 0, 说明是否成功.
- boolean free = exclusiveCount(nextc) == 0;
这里计算的只是 state 变量的低 16 的值, 而不是整个 state 的值. 虽然写的时候, 必然是串行的, 但这里计算的仍然是低 16 位的.
5. 总结
写锁在获取锁的时候, 有几个地方需要注意: 当有读锁的时候, 是不能获取写锁的. 写锁可以重入, 但不能超过 65535 次. 写锁状态设置在 state 变量的低 16 位.
同时, 在获取锁的时候, 也会根据公平与否决定此次释放需要获取锁.
如果是非公平的, 直接尝试 CAS 修改 state , 获取锁.
如果是公平的, 则根据 hasQueuedPredecessors 方法的返回值判断, 也就是如果队列中有等待的线程, 则依据公平策略, 放弃此次获取锁的操作. 反之, 直接获取锁.
来源: https://juejin.im/post/5ae755acf265da0b9526e2c5