多线程与高并发(六) 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()是用来获取锁的。这还还有一个土方法都是用来获取锁的,那有哪此区别呢?

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()土方法上。当前守护进程获取写锁完成数据准备事先,再获取读锁,就让释放写锁,完成锁降级。