多线程与高并发(六) Lock

  • 时间:
  • 浏览:0

日后学习了怎么可不可以使用synchronized关键字来实现同步访问,Java SE 5日后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字之类的同步功能,日后在使用时时要显式地获取和释放锁。有这些它缺少了(通过synchronized块意味方式所提供的)隐式获取释放锁的便捷性,有日后 却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步价值形式。

不同于synchronized是Java语言的关键字,是内置价值形式,Lock就有Java语言内置的,Lock是另一四个 类,通过这些 类后该 实现同步访问。有日后 synchronized同步块执行完成意味遇到异常是锁会自动释放,而lock时要调用unlock()方式释放锁,有日后 在finally块中释放锁。

一、 Lock 接口

先看看lock接口定义了那些方式:

void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();

这上方lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。这3个方式就有用来获取锁的,那有那些区别呢?

lock()方式是平常使用得最多的另一四个 方式,日后用来获取锁。意味锁已被某些线程 获取,则进行听候。

tryLock()方式是有返回值的,它表示用来尝试获取锁,意味获取成功,则返回true,意味获取失败(即锁已被某些线程 获取),则返回false,也日后这些 方式无论怎么可不可以就有立即返回。在拿后该 锁时无需一直在那听候。

tryLock(long time, TimeUnit unit)方式和tryLock()方式是之类的,只不过区别在于这些 方式在拿后该 锁就有听候一定的时间,在时间期限之内意味还拿后该 锁,就返回false。意味意味一现在现在日后刚开始拿到锁意味在听候期间内拿到了锁,则返回true。

lockInterruptibly()方式,当通过这些 方式去获取锁时,意味线程 正在听候获取锁,则这些 线程 后该 响应中断,即中断线程 的听候具体情况。也就使说,当另一四个 线程 一起去通过lock.lockInterruptibly()想获取某个锁时,假使 此时线程 A获取到了锁,而线程 B后该 在听候,太难 对线程 B调用threadB.interrupt()方式后该 中断线程 B的听候过程。

unLock()方式是用来释放锁的,这没那些一阵一阵时要讲的。

Condition newCondition() 是用于获取与lock绑定的听候通知组件,当前线程 时要获得了锁后该 进行听候,进行听候就有先释放锁,当再次获取锁时后该 从听候中返回。

Lock接口上方的方式大家意味知道,接下来实现Lock的类ReentrantLock现在现在日后刚开始学起,发现ReentrantLock并太难 几次代码,另外有另一四个 很明显的特点是:基本上所有的方式的实现实际上就有调用了其静态内存类Sync中的方式,而Sync类继承了AbstractQueuedSynchronizer(AQS)。

大家先学AQS相关的知识

二、AQS

AQS(以下简称同步器)是用来构建锁和某些同步组件的基础框架,它的实现主要依赖另一四个 int成员变量来表示同步具体情况,通过内置的FIFO队列来完成排队工作。

子类通过继承并实现它的抽象方式来管理同步具体情况,通过使用getState,setState以及compareAndSetState这另一四个 方式对同步具体情况进行更改。子类推荐被定义为自定义同步组件的静态內部类,同步器自身太难 实现任何同步接口,它仅仅是定义了若干同步具体情况的获取和释放方式来供自定义同步组件的使用,同步器既支持独占式获取同步具体情况,后该 能支持共享式获取同步具体情况,原先就后该 方便的实现不之类型的同步组件。

同步器是实现锁的关键,要实现锁功能,子类继承Lock,它定义了使用者与锁交互的接口,就像上方那几次接口,有日后 实现却是通过同步器,同步器简化了锁的实现方式,实现了底层操作,如同步具体情况管理,线程 的排队,听候和唤醒,而外面使用者去无需关心那些细节。

2.1 同步器的接口

同步器的设计模式是基于模板方式,也却一段话,使用者要继承同步器并重写指定的方式,日后将同步器组合在自定义同步器组合定义在自定义同步组件的实现中,并调用同步器提供的模板方式,而那些模板方式意味调用使用者重写的方式。总结日后同步器将某些方式开放给子类进行重写,而同步器给同步组件所提供模板方式又会重新调用被子类所重写的方式

如在AQS涵盖此方式:

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

而ReentrantLock中重写了方式:

那在AQS中的acquire调用了这些 方式,这就为宜在父类定义了一套模板,那些模板会调用某些可重写的方式,那些可重写的方式具体的实现倒入了子类。

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

这日后模板方式方式的设计思路,如还有疑惑,后该 去学习这些 设计模式。

下面日后某些后该 被重写的方式:

方式名称描述
protected boolean tryAcquire(int arg) 独占式获取同步具体情况,实现该方式时要查询当前具体情况并判断同步具体情况否有符合预期,有日后 再进行CAS设置同步具体情况
protected boolean tryRelease(int arg) 独占式释放同步具体情况,听候获取同步具体情况的线程 将有意味获取同步具体情况
protected int tryAcquireShared(int arg) 共享式获取同步具体情况,返回大于等于0的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg) 共享式释放同步具体情况
protected boolean isHeldExclusively() 当前同步器否有在独占模式下被线程 占用,一般该方式表示否有被当前线程 独占

实现自定义同步组件时,意味调用同步器提供的模板方式,那些(每种)模板方式与描述

方式名称描述
void acquire(int arg) 独占式获取同步具体情况,意味当前线程 获取同步具体情况成功,则由该方式返回,有日后 ,意味进入同步队列听候,该方式意味调用重写的tryAcquire(int arg)方式
void acquireInterruptibly(int arg) 与acquire(int arg)相同,有日后 该方式响应中断,当前线程 未获取到同步具体情况而进入同步队列中,意味当前线程 被中断,则该方式会抛出InterruptedException并返回
boolean tryAcquireNanos(int arg, long nanosTimeout) 在void acquireInterruptibly(int arg)的基础上增加了超时限制,意味当前线程 在超时时间内太难 获取到同步具体情况,太难 意味返回false,意味获取到了返回true
void acquireShared(int arg) 共享式的获取同步具体情况,意味当前线程 未获取到同步具体情况,意味进入同步队列听候,与独占式获取的主要区别是在同一时刻后该 有多个线程 获取到同步具体情况
void acquireSharedInterruptibly(int arg) 与acquireShared(int arg)相同,该方式响应中断
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) 在acquireSharedInterruptibly(int arg)基础上增加了超时限制
boolean release(int arg) 独占式的释放同步具体情况,该方式会在释放同步具体情况日后,将同步队列中第另一四个 节点涵盖的线程 唤醒
boolean releaseShared(int arg) 共享式的释放同步具体情况
Collection<Thread> getQueuedThreads() 获取听候在同步队列上的线程 集合

同步器提供的模板方式基本上分为3类:

  1. 独占式获取与释放同步具体情况

  2. 共享式获取与释放同步具体情况

  3. 查询同步队列中的听候线程 具体情况。

下面看另一四个 例子:

public class Mutex implements Lock {
 private static class Sync extends AbstractQueuedSynchronizer {
    // Reports whether in locked state
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }

    // Acquires the lock if state is zero
    public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    // Releases the lock by setting state to zero
    protected boolean tryRelease(int releases) {
        assert releases == 1; // Otherwise unused
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    // Provides a Condition
    Condition newCondition() {
        return new ConditionObject();
    }

    // Deserializes properly
    private void readObject(ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

private final Sync sync = new Sync();

@Override
public void lock() {
    sync.acquire(1);
}

@Override
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

@Override
public boolean tryLock() {
    return sync.tryAcquire(1);
}

@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(time));
}

@Override
public void unlock() {
    sync.release(1);
}

@Override
public Condition newCondition() {
    return sync.newCondition();
}
}

这些 例子中,独占锁Mutex是另一四个 自定义同步组件,它在同一时刻只允许另一四个 线程 占有锁。Mutex中定义了另一四个 静态內部类,该內部类继承了同步器并实现了独占式获取和释放同步具体情况。在tryAcquire(int acquires)方式中,意味经过CAS设置成功(同步具体情况设置为1),则代表获取了同步具体情况,而在tryRelease(int releases)方式中日后将同步具体情况重置为0。用户使用Mutex时从无需直接和內部同步器的实现打交道,日后调用Mutex提供的方式,在Mutex的实现中,以获取锁的lock()方式为例,只时要在方式实现中调用同步器的模板方式acquire(int args)即可,当前线程 调用该方式获取同步具体情况失败就有被加入到同步队列中听候,原先就大大降低了实现另一四个 可靠自定义同步组件的门槛。

2.2 同步队列

同步器依赖內部的同步队列(另一四个 FIFO双向队列)来完成同步具体情况的管理,当前线程 获取同步具体情况失败时,同步器会将当前线程 以及听候具体情况等信息构造成为另一四个 节点(Node)并将其加入同步队列,同就有阻塞当前线程 ,当同步具体情况释放时,会把首节点中的线程 唤醒,使其再次尝试获取同步具体情况。

同步队列中的节点(Node)用来保存获取同步具体情况失败的线程 引用、听候具体情况以及前驱和后继节点。

volatile int waitStatus //节点具体情况
volatile Node prev //当前节点/线程

的前驱节点
volatile Node next; //当前节点/线程

的后继节点
volatile Thread thread;//加入同步队列的线程

引用
Node nextWaiter;//听候队列中的下另一四个

节点

看后节点的数据价值形式,知道这是另一四个 双向队列,而在AQS中还处于另一四个 成员变量:

private transient volatile Node head;
private transient volatile Node tail;

AQS实际上通过头尾指针来管理同步队列,一起去实现包括获取锁失败的线程 进行入队,释放锁时对同步队列中的线程 进行通知等核心方式。其示意图如下:

通过对源码的理解以及做实验的方式,现在大家后该 清楚的知道原先几点:

  1. 节点的数据价值形式,即AQS的静态內部类Node,节点的听候具体情况等信息

  2. 同步队列是另一四个 双向队列,AQS通过持有头尾指针管理同步队列

三、 ReentrantLock

重入锁ReentrantLock,顾名思义,日后支持重进入的锁,它表示该锁后该 支持另一四个 线程 对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。意味另一四个 锁不支持可重入,那当另一四个 线程 调用它的lock()方式获取锁日后,意味再次调用lock()方式,则该线程 意味被当事人所阻塞。

synchronized关键字隐式的支持重进入,比如另一四个 synchronized修饰的递归方式,在方式执行时,执行线程 在获取了锁日后仍能连续多次地获得该锁。ReentrantLock有这些太难像synchronized关键字一样支持隐式的重进入,有日后 在调用lock()方式时,意味获取到锁的线程 ,后该 再次调用lock()方式获取锁而不被阻塞。

3.1 实现可重入性

重进入是指任意线程 在获取到锁日日后该 再次获取该锁而无需被锁所阻塞,该价值形式的实现时要出理 以下另一四个 大问题。

  1. 线程 再次获取锁。锁时要去识别获取锁的线程 否有为当前处于锁的线程 ,意味是,则再次成功获取。

  2. 锁的最终释放。线程 重复n次获取了锁,日后在第n次释放该锁后,某些线程 后该 获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁意味成功释放。

ReentrantLock是通过组合自定义同步器来实现锁的获取与释放,以非公平性(默认的)实现为例

核心方式为nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 意味该锁未被任何线程

占有,该锁能被当前线程

获取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //2.若被占有,检查占有线程

否有当前线程


    else if (current == getExclusiveOwnerThread()) {
        // 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

该方式增加了再次获取同步具体情况的出理 逻辑:通过判断当前线程 否有为获取锁的线程 来决定获取操作否有成功,意味是获取锁的线程 再次请求,则将同步具体情况值进行增加并返回true,表示获取同步具体情况成功。成功获取锁的线程 再次获取锁,日后增加了同步具体情况值,这也就要求ReentrantLock在释放同步具体情况时减少同步具体情况值。

protected final boolean tryRelease(int releases) {
    //1. 同步具体情况减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //2. 后该

当同步具体情况为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 3. 锁未被删改释放,返回false
    setState(c);
    return free;
}

意味该锁被获取了n次,太难 前(n-1)次tryRelease(int releases)方式时要返回false,而后该 同步具体情况删改释放了,后该 返回true。后该 看后,该方式将同步具体情况否有为0作为最终释放的条件,当同步具体情况为0时,将占有线程 设置为null,并返回true,表示释放成功。

3.2 公平否有公平获取锁的区别

公平锁非公平锁何谓公平性,是针对获取锁而言的,意味另一四个 锁是公平的,太难 锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO,ReentrantLock的构造方式无参时是构造非公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

另外还提供了另外有这些方式,可传入另一四个 boolean值,true时为公平锁,false时为非公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

在上方非公平锁获取时(nonfairTryAcquire方式)日后简单的获取了一下当前具体情况做了某些逻辑出理 ,并太难 考虑到当前同步队列中线程 听候的具体情况。大家来看看公平锁的出理 逻辑是怎么可不可以的,核心方式为:

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

这段代码的逻辑与nonfairTryAcquire基本上一直,唯一的不同在于增加了hasQueuedPredecessors的逻辑判断,方式名就可知道该方式用来判断当前节点在同步队列涵盖无有前驱节点的判断,意味有前驱节点说明有线程 比当前线程 更早的请求资源,根据公平性,当前线程 请求资源失败。意味当前节点太难 前驱节点一段话,再才有做上方的逻辑判断的必要性。公平锁每次就有从同步队列中的第另一四个 节点获取到锁,而非公平性锁则不一定,有意味刚释放锁的线程 能再次获取到锁

公平锁 VS 非公平锁

  1. 公平锁每次获取到锁为同步队列中的第另一四个 节点,保证请求资源时间上的绝对顺序,而非公平锁有意味刚释放锁的线程 下次继续获取该锁,则有意味意味某些线程 永远无法获取到锁,造成“饥饿”大问题

  2. 公平锁为了保证时间上的绝对顺序,时要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。有日后 ,ReentrantLock默认选择的是非公平锁,则是为了减少一每种上下文切换,保证了系统更大的吞吐量

四、 ReentrantReadWriteLock

日后学到的锁就有独占锁,那些锁在同一时刻只允许另一四个 线程 进行访问,而读写锁在同一时刻后该 允某些个读线程 访问,有日后 在写线程 访问时,所有的读线程 和某些写线程 均被阻塞。读写锁维护了一对锁,另一四个 读锁和另一四个 写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁后该 简化读写交互场景的编程方式。假设在多线程 中定义另一四个 共享的用作缓存数据价值形式,它大每种时间提供读服务(之类查询和搜索),而写操作占有的时间很少,有日后 写操作完成日后的更新时要对后续的读服务可见。

一般具体情况下,读写锁的性能就有比排它锁好,意味大多数场景读是多于写的。在读多于写的具体情况下,读写锁后该 提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock。

读写锁主要有以下另一四个 价值形式:

  1. 公平性选择:支持非公平性(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;

  2. 重入性:支持重入,读锁获取后该 再次获取,写锁获取日日后该 再次获取写锁,一起去也后该 获取读锁;

  3. 锁降级:遵循获取写锁,获取读锁再释放写锁的次序,写锁后该 降级成为读锁

4.1 读写锁的使用

ReadWriteLock仅定义了获取读锁和写锁的另一四个 方式,即readLock()方式和writeLock()方式,而有这些现——ReentrantReadWriteLock,除了接口方式之外,还提供了某些便于外界监控其內部工作具体情况的方式,主要有:

int getReadLockCount()//返回当前读锁被获取的次数。该次数不等于获取读锁的线程

数,意味另一四个

线程

连续获取n次,太难

返回的日后n
int getReadHoldCount()//返回当前线程

获取读锁的次数
boolean isWriteLocked()//判断写锁否有被获取
int getWriteHoldCount()//返回当前写锁被获取的次数

读写锁使用:

public class Cache {
    static Map<String, Object> map = new HashMap<>();
    static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    static Lock r = reentrantReadWriteLock.readLock();
    static Lock w = reentrantReadWriteLock.writeLock();
    // 获取另一四个

key对应的value
    public static final Object get(String key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
    // 设置key对应的value,并返回旧的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }
    // 清空所有的内容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}

Cache组合另一四个 非线程 安全的HashMap作为缓存的实现,一起去使用读写锁的读锁和写锁来保证Cache是线程 安全的。在读操作get(String key)方式中,时要获取读锁,这使得并发访问该方式时无需被阻塞。写操作put(String key,Object value)方式和clear()方式,在更新HashMap时时要提前获取写锁,当获取写锁后,某些线程 对于读锁和写锁的获取均被阻塞,而后该 写锁被释放日后,某些读写操作后该 继续。Cache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,一起去简化了编程方式。

4.2 实现原理

再分析下读写锁的实现原理,主要的内容包括:读写具体情况的设计,写锁的获取与释放,读锁的获取与释放以及锁降级。

读写具体情况的设计

读写锁同样依赖自定义同步器来实现同步功能,而读写具体情况日后其同步器的同步具体情况。回想ReentrantLock中自定义同步器的实现,同步具体具体情况示锁被另一四个 线程 重复获取的次数,而读写锁的自定义同步器时要在同步具体情况(另一四个 整型变量)上维护多个读线程 和另一四个 写线程 的具体情况,使得该具体情况的设计成为读写锁实现的关键。

意味在另一四个 整型变量上维护多种具体情况,就一定时要“按位切割使用”这些 变量,读写锁将变量切分成了另一四个 每种,高16位表示读,低16位表示写,如图:

写锁的获取与释放

写锁是另一四个 支持重进入的排它锁。意味当前线程 意味获取了写锁,则增加写具体情况。意味当前线程 在获取写锁时,读锁意味被获取(读具体情况不为0)意味该线程 就有意味获取写锁的线程 ,则当前线程 进入听候具体情况:

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // 1. 获取写锁当前的同步具体情况
    int c = getState();
    // 2. 获取写锁获取的次数
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // 3.1 当读锁已被读线程

获取意味当前线程

就有意味获取写锁的线程

一段话
        // 当前线程

获取写锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 3.2 当前线程

获取写锁,支持可重复加锁
        setState(c + acquires);
        return true;
    }
    // 3.3 写锁未被任何线程

获取,当前线程

可获取写锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

写锁的释放与ReentrantLock的释放过程基本之类,每次释放均减少写具体情况,当写具体情况为0时表示写锁已被释放,从而听候的读写线程 后该 继续访问读写锁,一起去前次写线程 的修改对后续读写线程 可见。

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //1. 同步具体情况减去写具体情况
    int nextc = getState() - releases;
    //2. 当前写具体情况否有为0,为0则释放写锁
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    //3. 不为0则更新同步具体情况
    setState(nextc);
    return free;
}

读锁的获取与释放

读锁是另一四个 支持重进入的共享锁,它后该 被多个线程 一起去获取,在太难 某些写线程 访问(意味写具体情况为0)时,读锁总会被成功地获取,而所做的也日后(线程 安全的)增加读具体情况。意味当前线程 意味获取了读锁,则增加读具体情况。意味当前线程 在获取读锁时,写锁已被某些线程 获取,则进入听候具体情况。另外意味要增加某些內部功能,比如getReadHoldCount()方式,作用是返回当前线程 获取读锁的次数。读具体情况是所有线程 获取读锁次数的总和,而每个线程 该人获取读锁的次数后该 选择保处于ThreadLocal中,由线程 自身维护,这使获取读锁的实现变得简化。

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    //1. 意味写锁意味被获取有日后

获取写锁的线程

就有当前线程

一段话,当前
    // 线程

获取读锁失败返回-1
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        //2. 当前线程

获取读锁
        compareAndSetState(c, c + SHARED_UNIT)) {
        //3. 下面的代码主日后新增的某些功能,比如getReadHoldCount()方式
        //返回当前获取读锁的次数
        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;
    }
    //4. 出理

在第二步中CAS操作失败的自旋意味实现重入性
    return fullTryAcquireShared(current);
}

读锁的每次释放(线程 安全的,意味有多个读线程 一起去释放读锁)均减少读具体情况,减少的 值是(1<<16)。

锁降级

锁降级指的是写锁降级成为读锁。意味当前线程 拥有写锁,有日后 将其释放,最后再获取读锁,这些 分段完成的过程后该 称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,日后释放(先前拥有的)写锁的过程。接下来看另一四个 锁降级的示例。意味数据不常变化,日后多个线程 后该 并发地进行数据出理 ,当数据变更后,意味当前线程 感知到数据变化,则进行数据的准备工作,一起去某些出理 线程 被阻塞,直到当前线程 完成数据的准备工作:

public void processData() {
readLock.lock();
if (!update) {
// 时要先释放读锁
readLock.unlock();
// 锁降级从写锁获取到现在现在日后刚开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}

当数据处于变更后,update变量(布尔类型且volatile修饰)被设置为false,此时所有访问processData()方式的线程 后该 感知到变化,但后该 另一四个 线程 后该 获取到写锁,某些线程 会被阻塞在读锁和写锁的lock()方式上。当前线程 获取写锁完成数据准备日后,再获取读锁,日后释放写锁,完成锁降级。