多线程与高并发(六) Lock

  • 时间:
  • 浏览:2

前一天学习了怎么使用synchronized关键字来实现同步访问,Java SE 5前一天,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字相似 的同步功能,可是我我在使用需用用显式地获取和释放锁。我我觉得它缺少了(通过synchronized块可能妙招所提供的)隐式获取释放锁的便捷性,过后 却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步底部形态。

不同于synchronized是Java语言的关键字,是内置底部形态,Lock都是 Java语言内置的,Lock是有另一一一一3个类,通过这些 类还需用实现同步访问。过后 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()妙招是平常使用得最多的有另一一一一3个妙招,可是我我用来获取锁。可能锁已被一点线程 获取,则进行等待时间。

tryLock()妙招是有返回值的,它表示用来尝试获取锁,可能获取成功,则返回true,可能获取失败(即锁已被一点线程 获取),则返回false,也可是我我这些 妙招无论怎么都是立即返回。在拿可不都过后 锁时过后一个劲在那等待时间。

tryLock(long time, TimeUnit unit)妙招和tryLock()妙招是相似 的,只不过区别在于这些 妙招在拿可不都过后 锁都是等待时间一定的时间,在时间期限之内可能还拿可不都过后 锁,就返回false。可能可能一结束了拿到锁可能等待时间时间期间内拿到了锁,则返回true。

lockInterruptibly()妙招,当通过这些 妙招去获取锁时,可能线程 正等待时间时间获取锁,则这些 线程 都都过后 响应中断,即中断线程 的等待时间情形。也就使说,当有另一一一一3个线程 共同通过lock.lockInterruptibly()想获取某个锁时,若果此时线程 A获取到了锁,而线程 B可不都过后 都过后 等待时间时间,那末对线程 B调用threadB.interrupt()妙招都都过后 中断线程 B的等待时间过程。

unLock()妙招是用来释放锁的,这没那先 很重需用讲的。

Condition newCondition() 是用于获取与lock绑定的等待时间通知组件,当前线程 需用获得了锁都过后 进行等待时间,进行等待时间都是先释放锁,当再次获取锁时都过后 从等待时间中返回。

Lock接口后边的妙招亲戚亲戚朋友可能知道,接下来实现Lock的类ReentrantLock结束了学起,发现ReentrantLock并那末哪有几个代码,另外有有另一一一一3个很明显的特点是:基本上所有的妙招的实现实际上都是 调用了其静态内存类Sync中的妙招,而Sync类继承了AbstractQueuedSynchronizer(AQS)。

亲戚亲戚朋友先学AQS相关的知识

二、AQS

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

子类通过继承并实现它的抽象妙招来管理同步情形,通过使用getState,setState以及compareAndSetState这有另一一一一3个妙招对同步情形进行更改。子类推荐被定义为自定义同步组件的静态内部人员类,同步器自身那末实现任何同步接口,它仅仅是定义了若干同步情形的获取和释放妙招来供自定义同步组件的使用,同步器既支持独占式获取同步情形,也还需用支持共享式获取同步情形,原来就还需用方便的实现不相似 型的同步组件。

同步器是实现锁的关键,要实现锁功能,子类继承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) 独占式的释放同步情形,该妙招会在释放同步情形前一天,将同步队列中第有另一一一一3个节点所含的线程 唤醒
boolean releaseShared(int arg) 共享式的释放同步情形
Collection<Thread> getQueuedThreads() 获取等待时间在同步队列上的线程 集合

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

  1. 独占式获取与释放同步情形

  2. 共享式获取与释放同步情形

  3. 查询同步队列中的等待时间线程 情形。

下面看有另一一一一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是有另一一一一3个自定义同步组件,它在同一时刻只允许有另一一一一3个线程 占有锁。Mutex中定义了有另一一一一3个静态内部人员类,该内部人员类继承了同步器并实现了独占式获取和释放同步情形。在tryAcquire(int acquires)妙招中,可能经过CAS设置成功(同步情形设置为1),则代表获取了同步情形,而在tryRelease(int releases)妙招中可是我我将同步情形重置为0。用户使用Mutex时并过后直接和内部人员同步器的实现打交道,可是我我调用Mutex提供的妙招,在Mutex的实现中,以获取锁的lock()妙招为例,只需用在妙招实现中调用同步器的模板妙招acquire(int args)即可,当前线程 调用该妙招获取同步情形失败都是被加入到同步队列中等待时间,原来就大大降低了实现有另一一一一3个可靠自定义同步组件的门槛。

2.2 同步队列

同步器依赖内部人员的同步队列(有另一一一一3个FIFO双向队列)来完成同步情形的管理,当前线程 获取同步情形失败时,同步器会将当前线程 以及等待时间情形等信息构造成为有另一一一一3个节点(Node)并将其加入同步队列,同都是阻塞当前线程 ,当同步情形释放时,会把首节点中的线程 唤醒,使其再次尝试获取同步情形。

同步队列中的节点(Node)用来保存获取同步情形失败的线程 引用、等待时间情形以及前驱和后继节点。

volatile int waitStatus //节点情形
volatile Node prev //当前节点/线程

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

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

引用
Node nextWaiter;//等待时间队列中的下有另一一一一3个节点

看多节点的数据底部形态,知道这是有另一一一一3个双向队列,而在AQS中还发生有另一一一一3个成员变量:

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

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

通过对源码的理解以及做实验的妙招,现在亲戚亲戚朋友还需用清楚的知道原来几点:

  1. 节点的数据底部形态,即AQS的静态内部人员类Node,节点的等待时间情形等信息

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

三、 ReentrantLock

重入锁ReentrantLock,顾名思义,可是我我支持重进入的锁,它表示该锁都都过后 支持有另一一一一3个线程 对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选折 。可能有另一一一一3个锁不支持可重入,那当有另一一一一3个线程 调用它的lock()妙招获取锁前一天,可能再次调用lock()妙招,则该线程 可能被当事人所阻塞。

synchronized关键字隐式的支持重进入,比如有另一一一一3个synchronized修饰的递归妙招,在妙招执行时,执行线程 在获取了锁前一天仍能连续多次地获得该锁。ReentrantLock我我觉得太难像synchronized关键字一样支持隐式的重进入,过后 在调用lock()妙招时,可能获取到锁的线程 ,都都过后 再次调用lock()妙招获取锁而不被阻塞。

3.1 实现可重入性

重进入是指任意线程 在获取到锁前一天都都过后 再次获取该锁而过后被锁所阻塞,该底部形态的实现需用处里以下有另一一一一3个大疑问。

  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 公平是否是 公平获取锁的区别

公平锁非公平锁何谓公平性,是针对获取锁而言的,可能有另一一一一3个锁是公平的,那末锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO,ReentrantLock的构造妙招无参时是构造非公平锁

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

另外还提供了另外并都是妙招,可传入有另一一一一3个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的逻辑判断,妙招名就可知道该妙招用来判断当前节点在同步队列中是否是 有前驱节点的判断,可能有前驱节点说明有线程 比当前线程 更早的请求资源,根据公平性,当前线程 请求资源失败。可能当前节点那末前驱节点一句话,再才有做后边的逻辑判断的必要性。公平锁每次都是 从同步队列中的第有另一一一一3个节点获取到锁,而非公平性锁则不一定,有可能刚释放锁的线程 能再次获取到锁

公平锁 VS 非公平锁

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

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

四、 ReentrantReadWriteLock

前一天学到的锁都是 独占锁,那先 锁在同一时刻只允许有另一一一一3个线程 进行访问,而读写锁在同一时刻还需用允一点个读线程 访问,过后 在写线程 访问时,所有的读线程 和一点写线程 均被阻塞。读写锁维护了一对锁,有另一一一一3个读锁和有另一一一一3个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁都都过后 冗杂读写交互场景的编程妙招。假设在多线程 中定义有另一一一一3个共享的用作缓存数据底部形态,它大每种时间提供读服务(相似 查询和搜索),而写操作占有的时间很少,过后 写操作完成前一天的更新需用对后续的读服务可见。

一般情形下,读写锁的性能都是比排它锁好,可能大多数场景读是多于写的。在读多于写的情形下,读写锁都都过后 提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock。

读写锁主要有以下有另一一一一3个底部形态:

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

  2. 重入性:支持重入,读锁获取还需用再次获取,写锁获取前一天都都过后 再次获取写锁,共同也都都过后 获取读锁;

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

4.1 读写锁的使用

ReadWriteLock仅定义了获取读锁和写锁的有另一一一一3个妙招,即readLock()妙招和writeLock()妙招,而我我觉得现——ReentrantReadWriteLock,除了接口妙招之外,还提供了一点便于外界监控其内部人员工作情形的妙招,主要有:

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

数,可能有另一一一一3个线程

连续获取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();
    // 获取有另一一一一3个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组合有另一一一一3个非线程 安全的HashMap作为缓存的实现,共同使用读写锁的读锁和写锁来保证Cache是线程 安全的。在读操作get(String key)妙招中,需用获取读锁,这使得并发访问该妙招时过后被阻塞。写操作put(String key,Object value)妙招和clear()妙招,在更新HashMap需用用提前获取写锁,当获取写锁后,一点线程 对于读锁和写锁的获取均被阻塞,而可不都过后 都过后 写锁被释放前一天,一点读写操作都过后 继续。Cache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,共同冗杂了编程妙招。

4.2 实现原理

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

读写情形的设计

读写锁同样依赖自定义同步器来实现同步功能,而读写情形可是我我其同步器的同步情形。回想ReentrantLock中自定义同步器的实现,同步情形表示锁被有另一一一一3个线程 重复获取的次数,而读写锁的自定义同步器需用在同步情形(有另一一一一3个整型变量)上维护多个读线程 和有另一一一一3个写线程 的情形,使得该情形的设计成为读写锁实现的关键。

可能在有另一一一一3个整型变量上维护多种情形,就一定需用“按位切割使用”这些 变量,读写锁将变量切分成了有另一一一一3个每种,高16位表示读,低16位表示写,如图:

写锁的获取与释放

写锁是有另一一一一3个支持重进入的排它锁。可能当前线程 可能获取了写锁,则增加写情形。可能当前线程 在获取写锁时,读锁可能被获取(读情形不为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;
}

读锁的获取与释放

读锁是有另一一一一3个支持重进入的共享锁,它都都过后 被多个线程 共同获取,在那末一点写线程 访问(可能写情形为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)。

锁降级

锁降级指的是写锁降级成为读锁。可能当前线程 拥有写锁,过后 将其释放,最后再获取读锁,这些 分段完成的过程可不都过后 称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,过后释放(先前拥有的)写锁的过程。接下来看有另一一一一3个锁降级的示例。可能数据不常变化,可是我多个线程 还需用并发地进行数据处里,当数据变更后,可能当前线程 感知到数据变化,则进行数据的准备工作,共同一点处里线程 被阻塞,直到当前线程 完成数据的准备工作:

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()妙招的线程 都都都过后 感知到变化,但可不都过后 都过后 有另一一一一3个线程 都都过后 获取到写锁,一点线程 会被阻塞在读锁和写锁的lock()妙招上。当前线程 获取写锁完成数据准备前一天,再获取读锁,过后释放写锁,完成锁降级。