回眸重探锁机制

468 阅读19分钟

1 前言

锁机制是一个大家经常谈到的问题,但又不是那么好把控,在之前的文章里面写过AQS原理解析;回头看时,仅仅是通过源码流程,并没有阐释,思考角度,性能安全等角度的考量,也没有数据结构状态的变化、更缺少了一些基础知识的归纳;这篇文章,是对之前写过文章的一些深入思考,会介绍下面几个方面

  • 各种锁概念
  • Thread类的介绍
  • LockSupport类的介绍
  • 原子操作介绍
  • AQS实现
  • android中那些锁

2 锁概念

2.1 乐观锁/悲观锁

乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度。

对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。

而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作;不加锁性能有很大提高

从概念来看,两者使用场景还是有很大差别

  1. 悲观锁,适合并发操作比较高,写场景比较多,数据需要准确性
  2. 乐观锁,适合并发操作少,读数据场景比较多,数据的准确性要求不高

乐观锁常见方式

  1. 版本形式,比如LiveData类中数据的更新时的分发
  2. 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方法即为执行主体;

常用方法

image.png

其它方法一些废弃了;一些我们平时很少用到;就比如上面的打断状态方法,其实是通过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方法,其对线程的操作是安全的;这里着重强调下是针对线程的暂停和启动,其它功能是没有的,没有其它功能,没有其它功能(不要意想,多想)

常用方法 image.png

其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的数据结构,具体如下:

image.png 也就是队列为双向链表;而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 添加节点

独占资源

image.png

共享资源

image.png

条件资源

image.png

6.5.2 线程执行异常或者被打断

独占资源

image.png

共享资源

image.png

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

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

  1. 当前state=0,AQS队列中无排队线程,且CAS操作state成功,则把记录当前线程
  2. 当前线程为上次纪律线程,则把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;
        }

获取锁资源成功条件

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

获取资源时,逻辑如下:

  1. state!=0,而排队的独占节点为0,或者不为记录线程;则获取失败,这时优先读锁
  2. state!=0,而排队中存在独占节点且为当前线程已经获得执行权,则获取成功
  3. 写公平模式或者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;
                }
            }
        }

共享资源获取,逻辑如下:

  1. 队列中存在独占节点,此时不是记录线程,则获取失败
  2. 非公平模式且CAS操作state成功,则获取成功
  3. 循环进行处理以下流程
  4. 队列中存在独占节点,此时不是记录线程,则获取失败
  5. 不存在独占节点且公平模式且不是第一个共享节点,持有锁数目为0时,获取失败
  6. CAS操作成功,则获取成功

在获取成功后,均会记录数据:

  1. 无共享节点,记录第一个线程对象,记录线程持有资源数目为1
  2. 当前线程为第一个共享节点线程时,其持有数目加1
  3. cachedHoldCounte为空时,从readHolds获取对象,并把其持有资源数目加1

公平读锁的可重入判断,和别的不一样;因为不能以当前记录线程比较作为依据,只能依据,是不是初次获取;

结语

本文章介绍中,有写内容介绍的并不是很详细,这也于笔者对其实现代码不详有关,不过参照官网解释和实际操作验证,应该是没有什么毛病,如果有误,也欢迎大家指正

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!