1 前言
锁机制是一个大家经常谈到的问题,但又不是那么好把控,在之前的文章里面写过AQS原理解析;回头看时,仅仅是通过源码流程,并没有阐释,思考角度,性能安全等角度的考量,也没有数据结构状态的变化、更缺少了一些基础知识的归纳;这篇文章,是对之前写过文章的一些深入思考,会介绍下面几个方面
- 各种锁概念
- Thread类的介绍
- LockSupport类的介绍
- 原子操作介绍
- AQS实现
- android中那些锁
2 锁概念
2.1 乐观锁/悲观锁
乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。
对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作;不加锁性能有很大提高
从概念来看,两者使用场景还是有很大差别
- 悲观锁,适合并发操作比较高,写场景比较多,数据需要准确性
- 乐观锁,适合并发操作少,读数据场景比较多,数据的准确性要求不高
乐观锁常见方式
- 版本形式,比如LiveData类中数据的更新时的分发
- CAS操作,比如利用java.util.concurrent.atomic原子类
2.2 自旋锁
首先说以下线程的阻塞和唤醒,是需要线程上下文切换,这是有一定消耗的。而循环查询当前资源状态进而获取锁以达到线程同步的策略,就是自旋锁,也即是以cpu消耗来替换线程切换消耗,最好是持有资源的时间比较小,这样进而节约消耗
这样看其缺点还是很明显的,就是循环时间过长,占用处理器时间过长,白白浪费了cpu资源。一般来说自旋要有一点的限度,达到限度后同样需要阻塞;因此有了一种更高级的自旋锁,也就是自适应自旋锁
自适应自旋锁,自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
2.3 无锁/偏向锁/轻量级锁/重量级锁
在java 1.6之前,Synchronized是一个重量级锁,性能笨重,大家很少使用;之后进行了锁的优化升级,这时Synchronized锁就存在四种状态:无锁、偏向锁、轻量级锁、重量级锁;
- 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。
- 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。在只有一个线程执行同步代码块时能够提高性能。
- 轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
- 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低
2.4 独占锁/共享锁
独占锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。可以基于AQS来实现
2.5 互斥锁/读写锁
这和独占锁、共享锁很类似;
- 互斥锁:独占锁,在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。
- 读写锁既是互斥锁,又是共享锁;处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。
2.6 可重入锁/不可重入锁
这个概念主要针对一个线程递归调用锁时,是否可以重复获取锁而不发生死锁;可以即为可重入锁,否则为不可重入锁;基于AQS和Synchronized关键字实现的均为可重入锁
2.7 公平锁/非公平锁
俗语理解,公平就是先到先得;不公平就是来的早不如来的巧,以及排队的按照排队顺序
公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
2.8 分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率
在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护
3 Thread类
多线程机制是由java虚拟机支持的,线程是实际计算任务的载体,由java虚拟机执行,用户往往创建新的线程来进行异步处理。java虚拟机需要线程存在,才能真正为用户工作。Thread类即为线程载体,调用start方法由native层进行创建以及其它的管理调用;线程执行方法:
默认实现
public void run() {
if (target != null) {
target.run();
}
}
也就是构造器中传入的Runnable接口实例中run方法即为执行主体;
常用方法
其它方法一些废弃了;一些我们平时很少用到;就比如上面的打断状态方法,其实是通过InterruptedException异常来触发的;
线程异常处理
采用UncaughtExceptionHandler机制进行处理;其android.jar包中处理流程如下
Thread类中
public final void dispatchUncaughtException(Throwable e) {
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler();
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
}
}
getUncaughtExceptionHandler().uncaughtException(this, e);
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
ThreadGroup类
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
也就是优先级: 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原理解析;这里会从以下几个方面来介绍
- 锁数据结构
- 获取锁、释放锁流程
我把锁分为独占锁、共享锁、条件锁,其实条件锁也是一种独占锁;但这里的独占锁也并不绝对,是因为,AQS本身是抽象类,而控制获取、释放的逻辑却留待用户实现;
6.1 数据结构
静态内部类Node即为AQS的数据结构,具体如下:
也就是队列为双向链表;而nextWaiter,是为条件锁准备的向后单链表;这个单链表各个部分的意义还是值得思考的
-
prev、next这两个指针域,就是排队等待的线程节点
-
数据域线程,方便使用LockSupport类进行暂停恢复
-
数据域等待状态:取消态、默认态、通知态、条件态以及传播态
- 取消态:线程执行异常或者被打断
- 通知态:当前线程等待被唤醒,去竞争资源
- 条件态:说明现在资源获取需要的额外条件不满足,需要等待
- 传播态:共享允许获取资源时,从当前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锁
可重入锁、独占锁;有两种模式:公平、非公平,可通过构造器进行设定;公平不公平,就是在获取资源执行权时,是否按照排队顺序优先获取来定的
公平锁
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());
else
acquire(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) // overflow
throw 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;
else
firstReaderHoldCount--;
} 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
公平读锁的可重入判断,和别的不一样;因为不能以当前记录线程比较作为依据,只能依据,是不是初次获取;
结语
本文章介绍中,有写内容介绍的并不是很详细,这也于笔者对其实现代码不详有关,不过参照官网解释和实际操作验证,应该是没有什么毛病,如果有误,也欢迎大家指正
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!