回眸重探锁机制,高级 android 工程师
public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}
void acquireSharedInterruptibly(int arg):获取共享锁,会抛出打断异常
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}
boolean tryAcquireSharedNanos(int arg, long nanosTimeout):获取锁有时间限制,超过则结束返回是否获取成功;会抛出打断异常
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);}
这里获取资源的条件是受 tryAcquireShared 的返回值来控制的,>=0 时,线程继续执行,否则,很可能会线程暂停; tryAcquireShared 方法需要用户实现,也会通过 AQS 中 state 变量来处理
protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();}
6.3.2 释放资源
释放时也只有一个入口
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}
tryReleaseShared 返回 true 时,才有可能去唤醒其它线程去竞争资源;也需要用户实现,同样,也是基于 AQS 中 state 变量来控制
protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();}
6.4 可重入
可重入性,是持有资源者,再次自动持有资源的行为;这些在独占锁锁中,只需要线程判断即可,而共享锁,则是不仅仅需要线程判断,还需要一些列共享持有者单独比对,无疑当前线程比对可以优化这个过程;线程的存储 AQS 已经提供方法,这些方法来源于其继承的抽象类 AbstractOwnableSynchronizer,而是否持有,则由方法 isHeldExclusively 提供,而这个方法需要用户实现
protected boolean isHeldExclusively() {throw new UnsupportedOperationException();}
6.5 数据变化
数据变化,涉及到几个重要方法:
addWaiter:从双端队列,队尾加入节点;线程获取资源失败时,会生成节点并加入队列;而条件锁在等待唤醒时被唤醒后,通过 enq 方法加入队列
shouldParkAfterFailedAcquire:决定是否暂停当前线程,在循环中处理
parkAndCheckInterrupt:暂停线程,并返回打断状态且重置打断状态
unparkSuccessor:唤醒等待资源的线程,进行资源竞争获取
6.5.1 添加节点
独占资源
共享资源
条件资源
6.5.2 线程执行异常或者被打断
独占资源
共享资源
6.5.3 等待状态
相对于 5.5.2,其中变化的仅仅 waitStatus = -1
6.5.4 条件唤醒状态
这是条件锁特有状态;实现原理是:首先节点在条件单列表条件链表中等待;两个等待循环条件为是否在资源竞争的双向列表中(就是独占/共享的队列),如果被唤醒,其就会在此队列了,然后调用独占锁的逻辑获取锁 其关键方法:
isOnSyncQueue:是否存在 AQS 中的双向队列中,被唤醒后存在
doSignal:移除条件单向列表列中头节点,并改变状态 waitStatus=0,加入 AQS 双向列表中去
6.6 条件锁使用条件
final int fullyRelease(Node node) {try {int savedState = getState();if (release(savedState))return savedState;throw new IllegalMonitorStateException();} catch (Throwable t) {node.waitStatus = Node.CANCELLED;throw t;}}
这个方法是在获取资源时均会调用的方法;会首先释放 state 个资源使用权,然后通过加入队列后通过相应方法获取 state 个使用权;释放不了 state 个使用权会直接报异常;因此,条件锁使用时,必须在当前已经获取资源使用权的情况使用下,且仅仅只有其自己获取资源使用权;
6.7 小结
其在性能上有许多要学习的地方:比如在获取资源执行权时,并没有立刻去暂停线程;在状态变化过程中,也没有仅仅考虑当前状态,也进行有可能的唤醒线程竞争、去除无效资源等;整体采用自旋+CAS 机制处理;
7 android 中的锁
7.1 synchronized 关键字
上面介绍锁概念时大致介绍了这个关键字,其通过编译时在代码块加入同步指令处理的;是以特殊的一个对象作为锁的标志:
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进
入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
通过锁对象来实现条件策略;这写方法来源于 Object 的 wait/notify/notifyAll 方法
7.2 ReentrantLock 锁
可重入锁、独占锁;有两种模式:公平、非公平,可通过构造器进行设定;公平不公平,就是在获取资源执行权时,是否按照排队顺序优先获取来定的
公平锁
final void lock() {acquire(1);}
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
这里有两种获取锁的方式:
当前 state=0,AQS 队列中无排队线程,且 CAS 操作 state 成功,则把记录当前线程
当前线程为上次纪律线程,则把 state 再次增加
为什么公平,就是因为,可获取资源时,先检查当前排队队列是否为空,为空才会获取
非公平锁
final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}
protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
获取锁资源成功条件
对 state 进行 CAS 操作,成功;记录线程
如果当前资源可被获取也即 state = 0 时,对 state 进行 CAS 操作成功;记录线程
当前线程为记录线程
唤醒竞争
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}
state 为 0 时,才会唤醒其它线程进行竞争;而不为 0 时,只是进行减少,这于可重入处理时相加相对应;所以加锁和解锁要成对出现
7.3 ReentrantReadWriteLock 锁
可重入锁;独占锁和共享锁共存; 读写锁;其读锁调用 AQS 的共享资源方法,写锁调用 AQS 独占资源方法;其也存在两种模式:公平、非公平,可通过构造器进行设定;
公平/非公平模式
对于写锁,公平时查看当前是否排队的,排队优先,非公平时,尝试获取资源的线程优先
对于读锁,公平模式时同样查看当前是否有排队的,排队优先,非公平时,根据排队等待的对头是否为独占节点
也即是依靠如下方法:返回 true 表示公平模式
abstract boolean readerShouldBlock();abstract boolean writerShouldBlock()
条件锁
读锁:直接抛出 UnsupportedOperationException 异常
写锁:使用 AQS 实现类调用 newCondition 生成
state 状态
读锁,通过 state 的低 16 位来确定个数;写锁通过 state 的高 16 位来确定
独占资源处理
protected final boolean tryRelease(int releases) {if (!isHeldExclusively())throw new IllegalMonitorStateException();int nextc = getState() - releases;boolean free = exclusiveCount(nextc) == 0;if (free)setExclusiveOwnerThread(null);setState(nextc);return free;}
唤醒处理和 ReentrantLock 没有区别
protected final boolean tryAcquire(int acquires) {Thread current = Thread.currentThread();int c = getState();int w = exclusiveCount(c);if (c != 0) {if (w == 0 || current != getExclusiveOwnerThread())return false;if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");setState(c + acquires);return true;}if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;setExclusiveOwnerThread(current);return true;}
获取资源时,逻辑如下:
state!=0,而排队的独占节点为 0,或者不为记录线程;则获取失败,这时优先读锁
state!=0,而排队中存在独占节点且为当前线程已经获得执行权,则获取成功
写公平模式或者 CAS 操作 state 失败,获取失败,否则成功
共享资源处理
这个相对于独占就复杂多了
protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();if (firstReader == current) {if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {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;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;if (compareAndSetState(c, nextc))return nextc == 0;}}
对是否需要唤醒时,首先需要处理共享的数据,然后才是 state 的 CAS 操作且是循环操作
共享数据: firstReader 共享节点的第一个线程对象,firstReader 线程对象对应的重入数目 firstReaderHoldCount;cachedHoldCounter:最后一个共享节点对象线程 tid 和持有重入数目;readHolds 为 HoldCounte 线程存储
CAS 循环操作的原因:因为读操作可能同时存在多个,保证 state 的安全
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);if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {if (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}return fullTryAcquireShared(current);}
final int fullTryAcquireShared(Thread current) {HoldCounter rh = null;for (;;) {int c = getState();if (exclusiveCount(c) != 0) {if (getExclusiveOwnerThread() != current)return -1;} else if (readerShouldBlock()) {if (firstReader == current) {} 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;}}}
共享资源获取,逻辑如下:
队列中存在独占节点,此时不是记录线程,则获取失败
非公平模式且 CAS 操作 state 成功,则获取成功
循环进行处理以下流程
队列中存在独占节点,此时不是记录线程,则获取失败
不存在独占节点且公平模式且不是第一个共享节点,持有锁数目为 0 时,获取失败
CAS 操作成功,则获取成功在获取成功后,均会记录数据:
无共享节点,记录第一个线程对象,记录线程持有资源数目为 1
当前线程为第一个共享节点线程时,其持有数目加 1
cachedHoldCounte 为空时,从 readHolds 获取对象,并把其持有资源数目加 1 公平读锁的可重入判断,和别的不一样;因为不能以当前记录线程比较作为依据,只能依据,是不是初次获取;结语--本文章介绍中,有写内容介绍的并不是很详细,这也于笔者对其实现代码不详有关,不过参照官网解释和实际操作验证,应该是没有什么毛病,如果有误,也欢迎大家指正
评论