多线程与高并发(六) 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并必须 2个代码,另外有必须 很明显的特点是:基本上所有的法律最好的办法 的实现实际上前要调用了其静态内存类Sync中的法律最好的办法 ,而Sync类继承了AbstractQueuedSynchronizer(AQS)。

亲们先学AQS相关的知识

二、AQS

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

子类通过继承并实现它的抽象法律最好的办法 来管理同步情况报告,通过使用getState,setState以及compareAndSetState这必须 法律最好的办法 对同步情况报告进行更改。子类推荐被定义为自定义同步组件的静态结构类,同步器自身必须 实现任何同步接口,它仅仅是定义了若干同步情况报告的获取和释放法律最好的办法 来供自定义同步组件的使用,同步器既支持独占式获取同步情况报告,也可不前要支持共享式获取同步情况报告,必须 就可不前要方便的实现不这类型的同步组件。

同步器是实现锁的关键,要实现锁功能,子类继承Lock,它定义了使用者与锁交互的接口,就像上端那2个接口,就让实现却是通过同步器,同步器多样化了锁的实现法律最好的办法 ,实现了底层操作,如同步情况报告管理,线程的排队,听候和唤醒,而外面使用者去前会关心哪此细节。

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()法律最好的办法 上。当前线程获取写锁完成数据准备以前,再获取读锁,就让释放写锁,完成锁降级。