多线程与高并发(六) Lock

  • 时间:
  • 浏览:1

之后学习了怎么使用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()法律法子上。当前守护程序获取写锁完成数据准备之后,再获取读锁,却说释放写锁,完成锁降级。