写点什么

回眸重探锁机制,跨平台移动开发的特点

用户头像
Android架构
关注
发布于: 2 小时前

}}}


也就是优先级: Thread.getUncaughtExceptionPreHandler() | thread.getUncaughtExceptionHandler() > threadGroup.uncaughtException > Thread.getDefaultUncaughtExceptionHandler()


我们自定义时,可以进行如下几种方式:


  • Thread 静态方法 setUncaughtExceptionPreHandler(UncaughtExceptionHandler eh),所有异常均会经过它的处理,但是不会抛出运行时异常和 Error 异常

  • Thread 成员函数 setUncaughtExceptionHandler(UncaughtExceptionHandler eh),仅仅针对当前线程

  • ThreadGroup 重写 void uncaughtException(Thread t, Throwable e),针对整个线程组处理

  • Thread 静态方法 setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh),针对所有线程,最后处理的默认方法

4 LockSupport 类

LockSupport 和 CAS 是 Java 并发包中很多并发工具控制机制的基础,它们底层其实都是依赖 Unsafe 实现,而 Unsafe 类不是直接提供给 android 开发,因此,我们构建线程安全只能依赖原子类和 LockSupport 类,而系统却可以使用 Unsafe 类


其操作针对线程,也就是线程的暂停也重新启动,而且相对 Thread 里面的 stop、resume 方法,其对线程的操作是安全的;这里着重强调下是针对线程的暂停和启动,其它功能是没有的,没有其它功能,没有其它功能(不要意想,多想)


常用方法



其 park、unpark 方法,都在对一个称作"许可"的东西,进行生产和消费,而其消费的地方,必须在本线程,生产的地方在其它均可,且只有在需要消费的时候生产,不重复生产;

5 原子类

首先需要连接一个概念:原子性,指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。而我们平时编写的代码,虽然是一行或者自己认为一次执行,但机器处理时并不一定时一步操作;


这些原子操作都在 java.util.concurrent.atomic 包下,其均是利用 sun.misc.Unsafe 来处理,其用法也是套路操作,也即是通过 Unsafe 类来操作值的方法,基本都是原子操作处理;特别的


boolean compareAndSet(T expect, T update)


expect 当前值,update 修改置,返回是否成功修改,也就是如果当前值与实际值是一致的,那么修改会成功;不难发现,这可能会发生 a-b-a 过程而继续修改成功;这个可以对数据加版本或者时间戳进而区别,而 AtomicStampedReference 就是为了解决这个问题

6 AQS 实现

这里不会有很多源码分析,有兴趣的可以查看[我 AbstractQueuedSynchronizer 原理解析](


);这里会从以下几个方面来介绍


  1. 锁数据结构

  2. 获取锁、释放锁流程


我把锁分为独占锁、共享锁、条件锁,其实条件锁也是一种独占锁;但这里的独占锁也并不绝对,是因为,AQS 本身是抽象类,而控制获取、释放的逻辑却留待用户实现;

6.1 数据结构

静态内部类 Node 即为 AQS 的数据结构,具体如下:



也就是队列为双向链表;而 nextWaiter,是为条件锁准备的向后单链表;这个单链表各个部分的意义还是值得思考的


  • prev、next 这两个指针域,就是排队等待的线程节点

  • 数据域线程,方便使用 LockSupport 类进行暂停恢复

  • 数据域等待状态:取消态、默认态、通知态、条件态以及传播态


  1. 取消态:线程执行异常或者被打断

  2. 通知态:当前线程等待被唤醒,去竞争资源

  3. 条件态:说明现在资源获取需要的额外条件不满足,需要等待

  4. 传播态:共享允许获取资源时,从当前 node 或者从头连续的 node 均可共享获取资源


  • nextWaiter:有两种职责;如果为空时标识独占节点,为 SHARED 时表示共享节点,其它值时表示条件等待节点数据结构(后面介绍条件锁时,会把这个结构单独列出)

6.2 独占锁

6.2.1 获取资源

可以通过下面几种方法来获取


  • void acquire(int arg):获取锁,arg 一般来说是 1


public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}


  • void acquireInterruptibly(int arg):获取锁,抛出打断异常


public final void acquireInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);}


  • boolean tryAcquireNanos(int arg, long nanosTimeout):获取锁存在最长时间,抛出打断异常


public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);}


可见,如果 tryAcquire 返回 true 时,相应方法也就结束了,线程后续方法即可继续执行,也就是获取锁了;而如果返回 false 时,后面方法则很大可能会触发线程暂停


protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}


tryAcquire 方法需要用户自实现;现有锁都是通过 AQS 中 state 整数变量来实现的

6.2.2 释放资源

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}


这里 tryRelease 返回 true 时,才有可能通知其它线程去竞争资源;


protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}


需要用户自实现;现有锁都是通过 AQS 中 state 整数变量来实现的

6.3 独占锁

6.3.1 获取资源

同样存在不同的入口


  • void acquireShared(int arg):获取共享锁


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 锁

可重入锁、独占锁


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


;有两种模式:公平、非公平,可通过构造器进行设定;公平不公平,就是在获取资源执行权时,是否按照排队顺序优先获取来定的


公平锁


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;}


这里有两种获取锁的方式:


  1. 当前 state=0,AQS 队列中无排队线程,且 CAS 操作 state 成功,则把记录当前线程

  2. 当前线程为上次纪律线程,则把 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;}


获取锁资源成功条件


  1. 对 state 进行 CAS 操作,成功;记录线程

  2. 如果当前资源可被获取也即 state = 0 时,对 state 进行 CAS 操作成功;记录线程

  3. 当前线程为记录线程


唤醒竞争


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 独占资源方法;其也存在两种模式:公平、非公平,可通过构造器进行设定;


公平/非公平模式


  1. 对于写锁,公平时查看当前是否排队的,排队优先,非公平时,尝试获取资源的线程优先

  2. 对于读锁,公平模式时同样查看当前是否有排队的,排队优先,非公平时,根据排队等待的对头是否为独占节点


也即是依靠如下方法:返回 true 表示公平模式


abstract boolean readerShouldBlock();abstract boolean writerShouldBlock()


条件锁


  1. 读锁:直接抛出 UnsupportedOperationException 异常

  2. 写锁:使用 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) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
回眸重探锁机制,跨平台移动开发的特点