并发编程之——写锁源码分析
Java 中的读写锁实现是 ReentrantReadWriteLock ,是一种锁分离策略。能有效提高读比写多的场景下的程序性能。
关于如何使用参见 并发编程之 Java 三把锁。
由于读写锁较为复杂,故分为篇文章进行源码分析,今天先说较为简单的写锁。
2. 写锁介绍
不论是读锁还是写锁,都是基于 AQS 的,而 AQS 留给子类实现的就是 tryAcquire 或者 tryAcquireShared 方法,前者是写锁的实现,后者是读锁的实现,从名字上可以看出,一个是独占锁,一个是共享锁。
今天看的就是 tryAcquire 和 tryRelease 的实现,获取和释放。
3. tryAcquire 实现
源码加注释如下:
1 | // 如果有读锁,此时是获取不到写锁的。当有写锁时,判断重入次数。 |
详解的逻辑都写在注释里了,可以对照源码查看,这里再次总结这个方法的逻辑。
首先判断锁是否空闲。
如果空闲,则根据公平与否判断是否应该获取锁,当
writerShouldBlock
返回结果是false
的时候,就使用 CAS 修改 state 变量,获取锁。成功之后修改 AQS 持有线程。如果不是空闲的,则判断写锁是否是空闲的,这里有 2 种情况:
3.1 如果写锁空闲,但
state
不是0,说明有读锁,那么就不能获取锁。3.2 如果写锁不空闲,判断持有 AQS 锁的线程是不是当前线程,如果不是,不能获取,反之,可以获取重入锁。
4. tryRelease 实现
代码加注释如下:
1 | protected final boolean tryRelease(int releases) { |
这里还是很简单的,只是有一个地方需要注意:
1 | // 计算写锁的状态,如果是0,说明是否成功。 |
这里计算的只是 state 变量的低 16 的值,而不是整个 state 的值。虽然写的时候,必然是串行的,但这里计算的仍然是低 16 位的。
5. 总结
写锁在获取锁的时候,有几个地方需要注意:当有读锁的时候,是不能获取写锁的。写锁可以重入,但不能超过 65535 次。写锁状态设置在 state 变量的低 16 位。
同时,在获取锁的时候,也会根据公平与否决定此次释放需要获取锁。
如果是非公平的,直接尝试CAS 修改 state ,获取锁。
如果是公平的,则根据 hasQueuedPredecessors 方法的返回值判断,也就是如果队列中有等待的线程,则依据公平策略,放弃此次获取锁的操作。反之,直接获取锁。