千家信息网

ReentrantLock源码分析

发表于:2025-02-06 作者:千家信息网编辑
千家信息网最后更新 2025年02月06日,本篇内容介绍了"ReentrantLock源码分析"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.
千家信息网最后更新 2025年02月06日ReentrantLock源码分析

本篇内容介绍了"ReentrantLock源码分析"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1.ReentrantLock类图结构

从类图我们可以直观地了解到,ReentrantLock最终还是使用AQS来实现地,并且根据参数来决定其内部是一个公平????还是非公平锁????,默认是非公平锁????。

public ReentrantLock() {    sync = new NonfairSync();}public ReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}

其中Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。

如果读者对AQS还不了解的话,可以去看看我的这篇文章:抽象同步队列AQS--AbstractQueuedSynchronizer锁详解

在这里,AQS的state状态值表示线程获取该锁的可重入次数,在默认情况下,state的值为0表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时,会尝试使用CAS设置state的值为1,

如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没用释放锁的情况下第二次获取该锁后,状态值被设置为2,这就是可重入次数。

在该线程释放锁时,会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放该锁。

2.获取锁的主要方法

2.1 void lock()方法

lock()获取锁,其实就是把state从0变成n(重入锁可以累加)。实际调用的是sync的lock方法,分公平和非公平。

public void lock() {    sync.lock();}

在如上代码中,ReentrantLock的lock()委托给sync类,根据创建的ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync,先看看sync的子类NonfairSync(非公平锁????)的情况

final void lock() {    if (compareAndSetState(0, 1))//CAS设置状态值为1        setExclusiveOwnerThread(Thread.currentThread());//设置该锁的持有者为当前线程    else //CAS失败的话        acquire(1);//调用AQS的acquire方法,传递参数为1}

下面是AQS的acquire的核心源码

public final void acquire(int arg) {    if (!tryAcquire(arg) &&//调用ReentantLock重写tryAcquire方法        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire返回false会把当前线程放入AQS阻塞队列        selfInterrupt();}

之前说过,AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自己定制化,所以这里代码会调用ReentantLock重写的tryAcquire方法。我们看下非公平锁????的实现

protected final boolean tryAcquire(int acquires) {    return nonfairTryAcquire(acquires);}
final boolean nonfairTryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    if (c == 0) {//当前AQS状态为0,acquires参数传递默认为1,因为之前CAS失败,再次获取锁        if (compareAndSetState(0, acquires)) {//CAS设置状态值为1            setExclusiveOwnerThread(current);//设置该锁的持有者为当前的线程            return true;        }    }    else if (current == getExclusiveOwnerThread()) {//如果当前线程是该锁的持有者        int nextc = c + acquires;//获取过了就累加,因为可重入        if (nextc < 0) // overflow//说明可重入次数溢出了            throw new Error("Maximum lock count exceeded");        setState(nextc);//重新设置锁的状态        return true;    }    return false;//如果当前线程不是该锁的持有者,则返回false,然后会放入AQS阻塞队列}

结束完非公平锁????的实现代码,回过头来看看非公平在这里是怎么体现的。首先非公平是说先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁????。

而是使用了抢夺策略。那么下面我们看看公平锁????是怎么实现公平的。

protected final boolean tryAcquire(int acquires) {        final Thread current = Thread.currentThread();        int c = getState();        if (c == 0) {//当前AQS状态为0            if (!hasQueuedPredecessors() &&//公平性策略,判断队列还有没有其它node,要保证公平                compareAndSetState(0, acquires)) {//CAS设置状态                setExclusiveOwnerThread(current);//设置获取锁的线程                return true;            }        }        else if (current == getExclusiveOwnerThread()) {//如果当前线程是该锁的持有者            int nextc = c + acquires;//重入次数+1            if (nextc < 0)                throw new Error("Maximum lock count exceeded");            setState(nextc);//重新设置锁的状态            return true;        }        return false;    }}

如上代码所示,公平的tryAcquire策略与非公平的类似,不同之处在于,代码在设置CAS操作之前添加了hasQueuedPredecessors()方法,该方法是实现公平性的核心代码。代码如下

public final boolean hasQueuedPredecessors() {       Node t = tail; // Read fields in reverse initialization order    Node h = head;    Node s;    return h != t &&        ((s = h.next) == null || s.thread != Thread.currentThread());}

2.2void lockInterruptibly()方法

该方法与lock()方法类似,不同在于对中断进行响应,如果当前线程在调用该方法时,其它线程调用了当前线程的interrupt()方法,则该线程抛出异常而返回

public void lockInterruptibly() throws InterruptedException {    sync.acquireInterruptibly(1);}
public final void acquireInterruptibly(int arg)        throws InterruptedException {    if (Thread.interrupted())//如果当前线程被中断,则直接抛出异常        throw new InterruptedException();    if (!tryAcquire(arg))//尝试获取资源        doAcquireInterruptibly(arg);//调用AQS可被中断的方法}

2.3 boolean tryLock()方法

尝试获取锁,如果当前锁没用被其它线程持有,则当前线程获取该锁并返回true,否则返回false。注意,该方法不会引起当前线程阻塞

public boolean tryLock() {    return sync.nonfairTryAcquire(1);}
final boolean nonfairTryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    if (c == 0) {        if (compareAndSetState(0, acquires)) {            setExclusiveOwnerThread(current);            return true;        }    }    else if (current == getExclusiveOwnerThread()) {        int nextc = c + acquires;        if (nextc < 0) // overflow            throw new Error("Maximum lock count exceeded");        setState(nextc);        return true;    }    return false;}

如上代码与非公平锁的tryAcquire()方法代码类似,所以tryLock()使用的是非公平策略。

2.4 boolean tryLock(long timeout, TimeUnit unit)方法

尝试获取锁,与tryLock()的不同之处在于,它设置了超时时间,如果超时时间到了,没用获取到锁,则返回false,以下是相关代码

public boolean tryLock(long timeout, TimeUnit unit)        throws InterruptedException {    return sync.tryAcquireNanos(1, unit.toNanos(timeout));//调用AQS的tryAcquireNanos方法}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)        throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    return tryAcquire(arg) ||        doAcquireNanos(arg, nanosTimeout);}
private boolean doAcquireNanos(int arg, long nanosTimeout)        throws InterruptedException {    if (nanosTimeout <= 0L)        return false;    final long deadline = System.nanoTime() + nanosTimeout;    final Node node = addWaiter(Node.EXCLUSIVE);    boolean failed = true;    try {        for (;;) {            final Node p = node.predecessor();            if (p == head && tryAcquire(arg)) {                setHead(node);                p.next = null; // help GC                failed = false;                return true;            }            nanosTimeout = deadline - System.nanoTime();            if (nanosTimeout <= 0L)                return false;            if (shouldParkAfterFailedAcquire(p, node) &&                nanosTimeout > spinForTimeoutThreshold)                LockSupport.parkNanos(this, nanosTimeout);            if (Thread.interrupted())                throw new InterruptedException();        }    } finally {        if (failed)            cancelAcquire(node);    }}

3 释放锁相关方法

3.1 void unlock()方法

尝试获取锁,如果当前线程持有锁,则调用该方法会让该线程持有的AQS状态值减1,如果减1后当前状态值为0,则当前线程会释放该锁,否则仅仅减1而已。

如果当前线程没用持有该锁而调用了该方法则会抛出异常,代码如下:

public void unlock() {    sync.release(1);}
public final boolean release(int arg) {    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);        return true;    }    return false;}

protected final boolean tryRelease(int releases) {    int c = getState() - releases;//AQS状态值减1    if (Thread.currentThread() != getExclusiveOwnerThread())        throw new IllegalMonitorStateException();    boolean free = false;    if (c == 0) {//如果当前可重入次数为0,则清空锁持有线程        free = true;        setExclusiveOwnerThread(null);    }    setState(c);//设置可重入次数为原始值减1    return free;}

4.案例介绍

下面使用ReentrantLock来实现一个简单的线程安全的list集合

public class ReentrantLockList {    //线程不安全的list    private ArrayListarrayList=new ArrayList<>();    //独占锁    private volatile ReentrantLock lock=new ReentrantLock();    //添加元素    public void add(String e){        lock.lock();        try {            arrayList.add(e);        }finally {            lock.unlock();        }    }    //删除元素    public void remove(String e){        lock.lock();        try {            arrayList.remove(e);        }finally {            lock.unlock();        }    }    //获取数据    public String get(int index){        lock.lock();        try {            return arrayList.get(index);        }finally {            lock.unlock();        }    }}

如上代码在操作arrayList元素前进行加锁保证同一时间只有一个线程可用对arrayList数组进行修改,但是也只能一个线程对arrayList进行访问。

如图,假如线程Thread-1,Thread-2,Thread-3同时尝试获取独占锁ReentrantLock,加上Thread-1获取到了????,则Thread-2和Thread-3就会被转换为Node节点并放入ReentrantLock对应的AQS阻塞队列,而后阻塞挂起。

如图,假设Thread-1获取锁后调用了对应的锁创建的条件变量1,那么Thread-1就会释放获取到的????,然后当前线程就会被转换为Node节点插入条件变量1的条件队列。由于Thread-1释放了????,所以阻塞到AQS队列里面的

Thread-2和Thread-3就会有机会获取到该锁,假如使用的是公平性策略,那么者时候Thread-2会获取到锁,从而从AQS队列里面移除Thread-2对应的Node节点。

"ReentrantLock源码分析"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

线程 方法 状态 代码 状态值 尝试 队列 持有者 次数 策略 阻塞 如上 情况 源码 不同 元素 公平性 参数 子类 时间 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 计算机网络技术 第六版 软件开发使用设备以及工艺流程 游戏软件开发外包合同 建网站怎么用国外的服务器 网络安全与执法与侦查学哪个好 数据库系统概论第5版第十一章 司法信息网络安全实务电子书 苏州龙明软件开发 华为集团财经软件开发工程师 漳州彼岸网络技术服务有限公司 iphone13连接不上服务器 学校维护网络安全工作方案 厦门网络安全科技有限公司 微信小程序数据库安全吗 北京市公安局数据库多久更新一次 星通网络技术有限公司 启用根服务器 病毒入侵服务器图片 数据库系统概念六 微盘 asp数据库访问技术简介 软件开发工程师简笔画大全 银行软件开发工资一般多少 内乡第三小学开展网络安全教育 如何将菜单数据输入数据库 如何复制服务器镜像 疫情期间网络安全知识提醒 数据库间传输有几种方式 服务器地址我的世界 政务服务网络安全会议发言稿 湖北交友软件开发哪家正规
0