在某个风和日丽的一天,某人品鉴Java并发之美,看到读写锁不以为然,太阳过于耀眼,眼睛一花,不知不觉把秘诀看反,并且觉得无比正确。
时隔一月,某日月黑风高,某人再次拜读并发之美,发现自己大错特错,泪流满面,悔不当初。遂连夜含泪写下此篇文章。
如有谬误,还请指正。
以ReentrantReadWriteLock作为一个开始吧。
import java.util.concurrent.locks.ReentrantReadWriteLock;
读写锁ReentrantReadWriteLock的家族:
ReadWriteLock是一个接口,内部由两个Lock接口组成
public interface ReadWriteLock{
Lock readLock();
Lock writeLock();
}
由ReentrantReadWriteLock实现了该接口。
如果想用读写锁,简单的来说大概是这样:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args){
ReadWriteLock rwL = new ReentrantReadWriteLock();
Lock read_lock = rwL.readLock();
read_lock.lock();
read_lock.unlock();
Lock write_lock = rwL.writeLock();
write_lock.lock();
write_lock.unlock();
}
}
读写锁的机制是这样的,读线程和读线程之间不用互斥,写线程和写线程之间互斥,读线程和写线程之间互斥。
读写锁具体是怎么实现的呢?
看ReentrantReadWriteLock源码:
下面的是两个构造方法,默认是非公平的。也可以指定是否公平。
public ReentrantReadWriteLock() {
this(false);
}
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
ReadLock和WriteLock的代码是一模一样的。
关键在于sync:
final Sync sync;
//Sync是一个内部抽象类,继承了AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
……
}
下面具体看Sync内部类的定义:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
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;
/** Returns the number of shared holds represented in count */
//持有读锁的线程的重入次数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }//>>>是逻辑右移
/** Returns the number of exclusive holds represented in count */
//持有写锁的线程的重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
……
大概意思就是把state变量拆两半,低16位记录写锁,高16位记录读锁。
这里有三个点:
- 写锁的16位表示什么
- 读锁的16位表示什么
- 为什么要把state变量拆两半来用
写锁的低16位表示一个写线程重入了多少次。
读锁的高16位表示有多少个线程拿到了读锁,也可以表示一个线程重入了多少次。
将一个int型变量拆成两半,是因为无法用一次CAS同时操作两个int变量。
ReadLock
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void unlock() {
sync.releaseShared(1);
}
acquireShared和releaseShared是AQS里的模板方法
AQS中的源码:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
WriteLock
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
AQS中的源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
最终的对应关系:
- 读锁的公平实现:Sync.tryAccquireShared() + FairSync
- 读锁的非公平实现:Sync.tryAccquireShared() + NonfairSync
- 写锁的公平实现:Sync.tryAccquire() + FairSync
- 写锁的非公平实现:Sync.tryAccquire() + NonfairSync
ReentrantReadWriteLock的内部类FairSync:
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
再看内部类NonfairSync:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
读锁获取步骤:
- 若写锁被其他线程持有,返回-1
- 根据公平非公平决定是否阻塞
- CAS获取锁,更新统计变量
写锁获取流程
- 若有线程获取了读锁,返回false
- 若拿到写锁的线程不是当前线程,返回false
- 若当前线程获取写锁的次数超过最大重入次数,抛出异常
- 若无线程获取写锁,CAS抢锁
总结
Java中的读写锁机制是读读不互斥,读写互斥,写写互斥。
主要实现类是ReentrantReadWriteLock,该类内部有两个内部类分别为ReadLock和WriteLock,
两个内部类通过调用AQS的模板方法来实现lock和unlock,模板方法由内部类Sync实现,并且Sync有两个子类来实现公平和非公平机制。读写锁通过获取state,来控制锁的获取,并且再根据读的共享和写的独占细化具体抢锁操作。