FBI WARNING(bushi)
当涉及sync调用时,并不会分析尝试获取和释放之后的后继逻辑,因为这个逻辑是由AQS类实现的。请看姊妹篇之并发入门组件AQS源码解析。
开始的开始是一个demo
以下的代码,会将独占锁持有5分钟,在此期会阻塞住后面两个线程的共享锁请求,独占锁释放后,两个线程会同时拥有共享锁,即使没有进行unlock()
public class lockTest {
public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
public static void main(String[] args) {
new Thread(()->{
writeLock.lock();
try {
Thread.sleep(300000);
System.out.println("写锁释放");
} catch (InterruptedException e) {
e.printStackTrace();
}
writeLock.unlock();
}).start();
new Thread(()->{
readLock.lock();
try {
System.out.println("读锁1");
Thread.sleep(300000);
} catch (Exception e) {
e.printStackTrace();
}
readLock.unlock();
}).start();
new Thread(()->{
readLock.lock();
try {
System.out.println("读锁1");
Thread.sleep(300000);
} catch (Exception e) {
e.printStackTrace();
}
readLock.unlock();
}).start();
}
}
ReentranReadWriteLock,writeLock,readLock,AQS的关系
让我们先进入第一行,
public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
可以看到,默认创建了一个非公平的AQS,然后将这个AQS分别给了读锁和写锁。
而后面两行
static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
就只是返回已经建立好的对象
独占锁获取
进入
我们进入上面的代码writeLock.lock();
进入了老熟客acquire方法。
核心方法
总体大概思路是,判断是否锁是否有被使用,如果有被使用而是自己,那重入,不然就失败,如果没被使用就设定为自己。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//获取状态并且检查独占进入次数
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// 独占次数为0,但是状态却不为0,那么说明现在存在着共享锁。如果独占锁不为自己,状态却不为0,那么说明现在存在着独占锁,这两种情况都无法获取到锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 达到MAX_COUNT说明重入了65536次,九成九九九都是代码写错了!
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 状态加上当前重入次数
setState(c + acquires);
return true;
}
//前一个条件是公平锁和非公平锁的关键差异。后一个,如果c发生了变化,说明有线程同样参与了该AQS状态的变化并且早于自己完成了修改,那么默认自己失败,放弃。
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//将独占锁所有者设定为自己,并返回成功获取
setExclusiveOwnerThread(current);
return true;
}
state
state是AQS里面一个32位长的整型。
private volatile int state;
后16位说明独占锁状态,数值表示这个独占锁被持有者重入了多少次。
前16位说明共享锁状态,数值表示这个共享锁被持有了多少次。(请注意,一个线程可以多次持有读锁)
公平与非公平的界定
我们可以清晰看到,公平和非公平只在于writerShouldBlock()和readerShouldBlock()的重写
先看看非公平锁,非公平锁并不会对独占进行操作
再看看公平锁,写锁会进行判断。如果队列不为空,并且队列里争抢头节点的线程不为自己,说明有线程在排队,于是tryAcquired失败,
独占锁释放
实际上release返回false并不重要,我们可以看出上层并没有对其返回值进行逻辑操作。顺带一提,unparkSuccessor也在AQS篇。
protected final boolean tryRelease(int releases) {
// 进行锁持有检验与释放
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 如果释放了之后不为0,说明重入了,那么在release层就直接返回false而不执行后续的唤醒后继者。
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
共享锁获取
进入
回到最初的最初,让我们看一下共享锁的获取流程
调用的是ReadLock下面的lock(),可以看出,也是对sync进行调用操作,只不过独占锁调用的是acquire,而共享锁调用的是acquireShared
try尝试获取一下
核心方法
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);
//如果成功获取,那么必定返回1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//如果缓存不是自己的HoldCounter,就去ThreadLocalMap取出线程私有的HoldCounter,存入缓存
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
// 如果自己缓存数量为0,就放入count为1的HoldCounter
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//如果不是因为存在独占锁,而是因为共享锁共同compareAndSetState导致失败,或者队列中首线程正在争抢独占锁(还没抢到),那么就会进入完全尝试
return fullTryAcquireShared(current);
}
如果头节点不为空,并且头节点的下一个不为空,并且头节点的下一个不是共享的,并且该节点没有被取消,那么就判定正在争抢锁的线程是抢独占锁的。如果对方抢独占锁,那就直接放行,这是为了避免前面判断完了非独占,但是到了这一行,其他线程又已经上了独占锁这种尴尬现象。
进入完全尝试
完全尝试分为三部分,一,有独占锁且不是自己,直接失败。二,自己没有获取过读锁只是首次尝试,直接返回false,不再尝试。三,前两者都通过的情况下,尝试成功获取锁。
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
//如果存在独占锁,但不是自己,直接失败,
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
//readerShouldBlock上面有解析,说明有线程在抢独占锁
} else if (readerShouldBlock()) {
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)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
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;
}
}
}
释放共享锁
进入
执行readLock.unlock();
核心方法
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果是第一个获取共享锁的,直接将firstReader改为null
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 否则取出当前线程的重入次数,并减一,如果只剩一次了,那么就从map中删除当前线程的计数器
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;
}
//一直尝试将状态值减1直到成功
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;
}
}