多线程与高并发(六) Lock

  • 时间:
  • 浏览:0
  • 来源:大发pk10_pk10直播_大发pk10直播

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

不同于synchronized是Java语言的关键字,是内置底部形态,Lock完会Java语言内置的,Lock是4个类,通过本身类可不时要实现同步访问。有随后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()办法是平常使用得最多的4个办法,本来用来获取锁。可能性锁已被其他守护程序运行运行获取,则进行等待歌曲。

tryLock()办法是有返回值的,它表示用来尝试获取锁,可能性获取成功,则返回true,可能性获取失败(即锁已被其他守护程序运行运行获取),则返回false,也本来本身办法无论如何完会立即返回。在拿很难 锁时我不要 老是 在那等待歌曲。

tryLock(long time, TimeUnit unit)办法和tryLock()办法是例如的,只不过区别在于本身办法在拿很难 锁完会等待歌曲一定的时间,在时间期限之内可能性还拿很难 锁,就返回false。可能性可能性一开始拿到锁可能性等待歌曲歌曲期间内拿到了锁,则返回true。

lockInterruptibly()办法,当通过本身办法去获取锁时,可能性守护程序运行运行正等待歌曲歌曲获取锁,则本身守护程序运行运行也能响应中断,即中断守护程序运行运行的等待歌曲具体情况。也就使说,当4个守护程序运行运行一起去通过lock.lockInterruptibly()想获取某个锁时,假如有一天此时守护程序运行运行A获取到了锁,而守护程序运行运行B很难 等待歌曲歌曲,很难 对守护程序运行运行B调用threadB.interrupt()办法也能中断守护程序运行运行B的等待歌曲过程。

unLock()办法是用来释放锁的,这没哪些不得劲时要讲的。

Condition newCondition() 是用于获取与lock绑定的等待歌曲通知组件,当前守护程序运行运行时要获得了锁也能进行等待歌曲,进行等待歌曲完会先释放锁,当再次获取锁时也能从等待歌曲中返回。

Lock接口上端的办法亲们可能性知道,接下来实现Lock的类ReentrantLock开始学起,发现ReentrantLock并很难 有几条代码,另外有4个很明显的特点是:基本上所有的办法的实现实际上完会调用了其静态内存类Sync中的办法,而Sync类继承了AbstractQueuedSynchronizer(AQS)。

亲们先学AQS相关的知识

二、AQS

AQS(以下简称同步器)是用来构建锁和其他同步组件的基础框架,它的实现主要依赖4个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) 独占式的释放同步具体情况,该办法会在释放同步具体情况随后,将同步队列中第4个节点饱含 的守护程序运行运行唤醒
boolean releaseShared(int arg) 共享式的释放同步具体情况
Collection<Thread> getQueuedThreads() 获取等待歌曲在同步队列上的守护程序运行运行集合

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

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

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

  3. 查询同步队列中的等待歌曲守护程序运行运行具体情况。

下面看4个例子:

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是4个自定义同步组件,它在同一时刻只允许4个守护程序运行运行占有锁。Mutex中定义了4个静态内内外部类,该内内外部类继承了同步器并实现了独占式获取和释放同步具体情况。在tryAcquire(int acquires)办法中,可能性经过CAS设置成功(同步具体情况设置为1),则代表获取了同步具体情况,而在tryRelease(int releases)办法中本来将同步具体情况重置为0。用户使用Mutex时何必 会直接和内内外部同步器的实现打交道,本来调用Mutex提供的办法,在Mutex的实现中,以获取锁的lock()办法为例,只时要在办法实现中调用同步器的模板办法acquire(int args)即可,当前守护程序运行运行调用该办法获取同步具体情况失败完会被加入到同步队列中等待歌曲,原来就大大降低了实现4个可靠自定义同步组件的门槛。

2.2 同步队列

同步器依赖内内外部的同步队列(4个FIFO双向队列)来完成同步具体情况的管理,当前守护程序运行运行获取同步具体情况失败时,同步器会将当前守护程序运行运行以及等待歌曲具体情况等信息构造成为4个节点(Node)并将其加入同步队列,同完会阻塞当前守护程序运行运行,当同步具体情况释放时,会把首节点中的守护程序运行运行唤醒,使其再次尝试获取同步具体情况。

同步队列中的节点(Node)用来保存获取同步具体情况失败的守护程序运行运行引用、等待歌曲具体情况以及前驱和后继节点。

volatile int waitStatus //节点具体情况
volatile Node prev //当前节点/守护程序运行运行的前驱节点
volatile Node next; //当前节点/守护程序运行运行的后继节点
volatile Thread thread;//加入同步队列的守护程序运行运行引用
Node nextWaiter;//等待歌曲队列中的下4个节点

想看 节点的数据底部形态,知道这是4个双向队列,而在AQS中还处于4个成员变量:

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

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

通过对源码的理解以及做实验的办法,现在亲们可不时要清楚的知道原来几点:

  1. 节点的数据底部形态,即AQS的静态内内外部类Node,节点的等待歌曲具体情况等信息

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

三、 ReentrantLock

重入锁ReentrantLock,顾名思义,本来支持重进入的锁,它表示该锁也能支持4个守护程序运行运行对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选折 。可能性4个锁不支持可重入,那当4个守护程序运行运行调用它的lock()办法获取锁随后,可能性再次调用lock()办法,则该守护程序运行运行可能性被个人所阻塞。

synchronized关键字隐式的支持重进入,比如4个synchronized修饰的递归办法,在办法执行时,执行守护程序运行运行在获取了锁随后仍能连续多次地获得该锁。ReentrantLock并不一定很难像synchronized关键字一样支持隐式的重进入,有随后在调用lock()办法时,可能性获取到锁的守护程序运行运行,也能再次调用lock()办法获取锁而不被阻塞。

3.1 实现可重入性

重进入是指任意守护程序运行运行在获取到锁随后也能再次获取该锁而我不要 被锁所阻塞,该底部形态的实现时要处置以下4个难题。

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

公平锁非公平锁何谓公平性,是针对获取锁而言的,可能性4个锁是公平的,很难 锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO,ReentrantLock的构造办法无参时是构造非公平锁

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

另外还提供了另外本身生活办法,可传入4个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的逻辑判断,办法名就可知道该办法用来判断当前节点在同步队列中否是有前驱节点的判断,可能性有前驱节点说明有守护程序运行运行比当前守护程序运行运行更早的请求资源,根据公平性,当前守护程序运行运行请求资源失败。可能性当前节点很难 前驱节点搞笑的话,再才有做上端的逻辑判断的必要性。公平锁每次完会从同步队列中的第4个节点获取到锁,而非公平性锁则不一定,有可能性刚释放锁的守护程序运行运行能再次获取到锁

公平锁 VS 非公平锁

  1. 公平锁每次获取到锁为同步队列中的第4个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能性刚释放锁的守护程序运行运行下次继续获取该锁,则有可能性原应其他守护程序运行运行永远无法获取到锁,造成“饥饿”难题

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

四、 ReentrantReadWriteLock

随后学到的锁完会独占锁,哪些锁在同一时刻只允许4个守护程序运行运行进行访问,而读写锁在同一时刻可不时要允其他个读守护程序运行运行访问,有随后在写守护程序运行运行访问时,所有的读守护程序运行运行和其他写守护程序运行运行均被阻塞。读写锁维护了一对锁,4个读锁和4个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁也能僵化 读写交互场景的编程办法。假设在守护程序运行运行中定义4个共享的用作缓存数据底部形态,它大累积时间提供读服务(例如查询和搜索),而写操作占有的时间很少,有随后写操作完成随后的更新时要对后续的读服务可见。

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

读写锁主要有以下4个底部形态:

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

  2. 重入性:支持重入,读锁获取可不时要再次获取,写锁获取随后也能再次获取写锁,一起去也也能获取读锁;

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

4.1 读写锁的使用

ReadWriteLock仅定义了获取读锁和写锁的4个办法,即readLock()办法和writeLock()办法,而并不一定现——ReentrantReadWriteLock,除了接口办法之外,还提供了其他便于外界监控其内内外部工作具体情况的办法,主要有:

int getReadLockCount()//返回当前读锁被获取的次数。该次数不等于获取读锁的守护程序运行运行数,可能性4个守护程序运行运行连续获取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();
    // 获取4个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组合4个非守护程序运行运行安全的HashMap作为缓存的实现,一起去使用读写锁的读锁和写锁来保证Cache是守护程序运行运行安全的。在读操作get(String key)办法中,时要获取读锁,这使得并发访问该办法时我不要 被阻塞。写操作put(String key,Object value)办法和clear()办法,在更新HashMap时时要提前获取写锁,当获取写锁后,其他守护程序运行运行对于读锁和写锁的获取均被阻塞,而很难 写锁被释放随后,其他读写操作也能继续。Cache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,一起去僵化 了编程办法。

4.2 实现原理

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

读写具体情况的设计

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

可能性在4个整型变量上维护多种具体情况,就一定时要“按位切割使用”本身变量,读写锁将变量切分成了4个累积,高16位表示读,低16位表示写,如图:

写锁的获取与释放

写锁是4个支持重进入的排它锁。可能性当前守护程序运行运行可能性获取了写锁,则增加写具体情况。可能性当前守护程序运行运行在获取写锁时,读锁可能性被获取(读具体情况不为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;
}

读锁的获取与释放

读锁是4个支持重进入的共享锁,它也能被多个守护程序运行运行一起去获取,在很难 其他写守护程序运行运行访问(可能性写具体情况为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)。

锁降级

锁降级指的是写锁降级成为读锁。可能性当前守护程序运行运行拥有写锁,有随后将其释放,最后再获取读锁,本身分段完成的过程很难 称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,完会释放(先前拥有的)写锁的过程。接下来看4个锁降级的示例。可能性数据不常变化,本来多个守护程序运行运行可不时要并发地进行数据处置,当数据变更后,可能性当前守护程序运行运行感知到数据变化,则进行数据的准备工作,一起去其他处置守护程序运行运行被阻塞,直到当前守护程序运行运行完成数据的准备工作:

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()办法的守护程序运行运行都也能感知到变化,但很难 4个守护程序运行运行也能获取到写锁,其他守护程序运行运行会被阻塞在读锁和写锁的lock()办法上。当前守护程序运行运行获取写锁完成数据准备随后,再获取读锁,完会释放写锁,完成锁降级。