引言

由ReentrantReadWriteLock源码可知,读锁与写锁共同使用了一个Sync同步器,即使用了同一个同步队列。

获取锁

获取读锁

1
2
3
public void lock() {
sync.acquireShared(1);
}

直接调用了同步器的共享锁。AQS中的acquireShared方法会回调Sync的tryAcquireShared(int unused)方法。

1
2
3
4
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}

tryAcquireShared方法相对来说就比较复杂了,连蒙带猜写的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* 共享模式获取锁,当前锁可以被多个线程所持有,state代表当前锁的持有线程数量
* @param unused 获取锁的数量
* @return
*/
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 如果另一个线程持有写锁,则失败。
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy.
* 否则,这个线程就符合锁定wrt状态的条件,所以请询问它是否应该由于队列策略而阻塞。
* If not, try to grant by CASing state and updating count.
* 如果不是,尝试用CAS更新数量。
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 注意,那一步不检查可重入获取,它被推迟到完整版本,以避免在更典型的不可重入情况下检查hold count
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
* 如果第2步失败,要么是因为线程显然不符合条件,要么是因为CAS失败或计数饱和,则使用完整的重试循环链接到版本。
*/
//获取当前的线程
Thread current = Thread.currentThread();
//获取当前锁的状态
int c = getState();
//c中表示独占持有的数量不为0:表示有写锁
//当前线程并不是获取写锁的线程
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
//获取锁失败
return -1;
//c中表示的共享持有的数量
int r = sharedCount(c);
//查看读锁是否应该阻塞
//读锁的数量是否超过限制
//尝试修改锁状态值是否成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//读锁不阻塞且读锁数量不超过最大值,并且获取读锁成功

//如果r=0
if (r == 0) {
//当前线程为第一个读者
firstReader = current;
//读锁持有数量为1
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//如果发现当前线程就是第一个读者,那就将锁持有数量+1
firstReaderHoldCount++;
} else {
//不是第一个读者,获取读锁持有线程计数器
HoldCounter rh = cachedHoldCounter;
//如果还没有计数器,或者当前线程的线程Id与计数器的不同
if (rh == null || rh.tid != getThreadId(current))
//再获取一个计数器,readHolds为ThreadLocal的子类,线程共享
cachedHoldCounter = rh = readHolds.get();
//如果计数器的持有值为0
else if (rh.count == 0)
//将计数器保存到ThreadLocal
readHolds.set(rh);
//计数器的值+1
rh.count++;
}
//获取锁成功
return 1;
}
//如果不满足条件:读锁不阻塞且读锁数量不超过最大值,并且获取读锁成功
//则执行下面的方法
return fullTryAcquireShared(current);
}

fullTryAcquireShared方法和tryAcquireShared方法冗余度确实挺高的,只有满足以下几个条件,才会进入完整版本获取锁方法:

  • 没有写锁
  • 读阻塞readerShouldBlock方法返回true
  • 读锁持有线程数量大于等于MAX_COUNT
  • 尝试获取读锁失败
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    /**
    * Full version of acquire for reads, that handles CAS misses
    * and reentrant reads not dealt with in tryAcquireShared.
    * 获取读锁更完整版本,处理tryAcquireShared中没有解决的CAS misses和可重入读
    */
    final int fullTryAcquireShared(Thread current) {
    /*
    * This code is in part redundant with that in
    * tryAcquireShared but is simpler overall by not
    * complicating tryAcquireShared with interactions between
    * retries and lazily reading hold counts.
    * 这段代码与tryAcquireShared中的代码在一定程度上是冗余的,但总的来说更简单,
    * 因为它没有使tryAcquireShared在重试和延迟读取hold count之间的交互复杂化。
    */
    HoldCounter rh = null;
    //循环
    for (; ; ) {
    //获取当前锁状态
    int c = getState();
    //c中表示独占持有的数量不为0,并且当前线程并不是获取写锁的线程。
    if (exclusiveCount(c) != 0) {
    if (getExclusiveOwnerThread() != current)
    return -1;
    // else we hold the exclusive lock; blocking here
    // would cause deadlock.
    //查看是否阻塞读锁
    } else if (readerShouldBlock()) {
    // Make sure we're not acquiring read lock reentrantly
    // 确保我们没有重入读锁
    if (firstReader == current) {
    // assert firstReaderHoldCount > 0;
    } else {
    if (rh == null) {
    rh = cachedHoldCounter;
    //获取当前线线程所持有的读锁数量
    if (rh == null || rh.tid != getThreadId(current)) {
    rh = readHolds.get();
    if (rh.count == 0)
    //ThreadLocal移除当前线程
    readHolds.remove();
    }
    }
    //读锁数量为0
    if (rh.count == 0)
    return -1;
    }
    }
    //如果读锁数量等罪最大值,抛出Error
    if (sharedCount(c) == MAX_COUNT)
    throw new Error("Maximum lock count exceeded");
    //CAS尝试获取读锁
    if (compareAndSetState(c, c + SHARED_UNIT)) {
    //如果读锁数量为0
    if (sharedCount(c) == 0) {
    //设置第一个读者
    firstReader = current;
    firstReaderHoldCount = 1;
    } else if (firstReader == current) {
    //第一个读者重入
    firstReaderHoldCount++;
    } else {
    //不是第一个读者,使用计数器
    if (rh == null)
    rh = cachedHoldCounter;
    if (rh == null || rh.tid != getThreadId(current))
    rh = readHolds.get();
    else if (rh.count == 0)
    readHolds.set(rh);
    rh.count++;
    cachedHoldCounter = rh; // cache for release
    }
    return 1;
    }
    }
    }

获取写锁

直接看代码注释即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 独占模式获取锁
* @param acquires 获取的数量
* @return
*/
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 如果读计数非零或写计数非零且所有者是另一个线程,则失败。
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 如果计数饱和,则失败。(只有当count已经非零时才会发生这种情况。)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
* 否则,如果这个线程是可重入获取或队列策略允许,那么它就有资格获得锁。
* 如果是,则更新状态并设置所有者。
*/
//获取当前线程
Thread current = Thread.currentThread();
//获取当前锁状态
int c = getState();
//获取独占锁持有数量
int w = exclusiveCount(c);
//c!=0 表示有线程持有锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 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;
}
//如果c=0且(写阻塞或者CAS获取锁失败)
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//c=0,写不阻塞,CAS获取锁成功,设置独占线程标志位
setExclusiveOwnerThread(current);
return true;
}

释放锁

释放读锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 共享模式释放锁
* @param unused
* @return
*/
protected final boolean tryReleaseShared(int unused) {
//获取当前线程
Thread current = Thread.currentThread();
//如果当前线程是第一个读者
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
//如果此线程重入次数为1,则制空第一读者标志位
if (firstReaderHoldCount == 1)
firstReader = null;
else
//重入次数减1
firstReaderHoldCount--;
} else {
//如果当前线程不是第一读者,去要从ThreadLocal中获取计数器,并做减一操作
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//循环加CAS套路,释放锁
for (; ; ) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}

private IllegalMonitorStateException unmatchedUnlockException() {
return new IllegalMonitorStateException(
"attempt to unlock read lock, not locked by current thread");
}

释放写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
* 注意,tryRelease和tryacquisition可以根据条件调用。
* 因此,它们的参数可能同时包含读和写持有,
* 这些持有都在一个条件等待期间释放,并在tryAcquire中重新建立。
*/
protected final boolean tryRelease(int releases) {
//判断当前线程是不是持有读锁的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//释放锁的次数
int nextc = getState() - releases;
//如果持有写锁的数量为0了,即当前没有写锁
boolean free = exclusiveCount(nextc) == 0;
if (free)
//将互斥锁标志位置为null
setExclusiveOwnerThread(null);
//设置线程
setState(nextc);
return free;
}

从获取锁与释放锁的源码中可以看出几个比较重要的属性:

  • firstReader和firstReaderHoldCount
    使用这两个属性的目的应该是优化性能,因为读锁重入或者读锁释放的时候可以直接操作,而不必去用ThreadLocal。
  • cachedHoldCounter和readHolds
    保存了读锁持有的非第一读者的其他线程与其持有的重入数量。
  • exclusiveOwnerThread
    存储独占模式同步的当前所有者。