怎么深入理解ReentrantLock原理

发布时间:2021-12-03 16:05:02 作者:柒染
来源:亿速云 阅读:166

这期内容当中小编将会给大家带来有关怎么深入理解ReentrantLock原理,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

ReentrantLock是什么?

ReentrantLock是个典型的独占模式AQS,同步状态为0时表示空闲。当有线程获取到空闲的同步状态时,它会将同步状态加1,将同步状态改为非空闲,于是其他线程挂起等待。在修改同步状态的同时,并记录下自己的线程,作为后续重入的依据,即一个线程持有某个对象的锁时,再次去获取这个对象的锁是可以成功的。如果是不可重入的锁的话,就会造成死锁。

ReentrantLock会涉及到公平锁和非公平锁,实现关键在于成员变量sync的实现不同,这是锁实现互斥同步的核心。

 //公平锁和非公平锁的变量
 private final Sync sync;
 //父类
 abstract static class Sync extends AbstractQueuedSynchronizer {}
 //公平锁子类
 static final class FairSync extends Sync {}
 //非公平锁子类
 static final class NonfairSync extends Sync {}

那公平锁和非公平锁是什么?有什么区别?

那公平锁和非公平锁是什么?有什么区别?

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权,即先进先出。而非公平锁则随机分配这种使用权,是一种抢占机制,是随机获得锁,并不是先来的一定能先得到锁。

ReentrantLock提供了一个构造方法,可以实现公平锁或非公平锁:

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

虽然公平锁在公平性得以保障,但因为公平的获取锁没有考虑到操作系统对线程的调度因素以及其他因素,会影响性能。

虽然非公平模式效率比较高,但是非公平模式在申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁,这就是非公平锁的“饥饿”问题。

但大部分情况下我们使用非公平锁,因为其性能比公平锁好很多。但是公平锁能够避免线程饥饿,某些情况下也很有用。

接下来看看ReentrantLock公平锁的实现:

ReentrantLock::lock公平锁模式实现

怎么深入理解ReentrantLock原理

首先需要在构建函数中传入true创建好公平锁

ReentrantLock reentrantLock = new ReentrantLock(true);

调用lock()进行上锁,直接acquire(1)上锁

public void lock() {
    // 调用的sync的子类FairSync的lock()方法:ReentrantLock.FairSync.lock()
    sync.lock();
}
final void lock() {
    // 调用AQS的acquire()方法获取锁,传的值为1
    acquire(1);
}

直接尝试获取锁,

// AbstractQueuedSynchronizer.acquire()
public final void acquire(int arg) {
    // 尝试获取锁
    // 如果失败了,就排队
    if (!tryAcquire(arg) &&
        // 注意addWaiter()这里传入的节点模式为独占模式
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

具体获取锁流程

// ReentrantLock.FairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 状态变量的值为0,说明暂时还没有线程占有锁
    if (c == 0) {
        // hasQueuedPredecessors()保证了不论是新的线程还是已经排队的线程都顺序使用锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 当前线程获取了锁,并将本线程设置到exclusiveOwnerThread变量中,
            //供后续自己可重入获取锁作准备
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    
    // 之所以说是重入锁,就是因为在获取锁失败的情况下,还会再次判断是否当前线程已经持有锁了
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 设置到state中
        // 因为当前线程占有着锁,其它线程只会CAS把state从0更新成1,是不会成功的
        // 所以不存在竞争,自然不需要使用CAS来更新
        setState(nextc);
        return true;
    }
    return false;
}

如果获取失败加入队列里,那具体怎么处理呢?通过自旋的方式,队列中线程不断进行尝试获取锁操作,中间是可以通过中断的方式打断,

在看完获取锁的流程,那么你知道ReentrantLock如何实现公平锁了吗?其实就是在tryAcquire()的实现中。

ReentrantLock如何实现公平锁?

tryAcquire()的实现中使用了hasQueuedPredecessors()保证了线程先进先出FIFO的使用锁,不会产生"饥饿"问题,

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 状态变量的值为0,说明暂时还没有线程占有锁
    if (c == 0) {
        // hasQueuedPredecessors()保证了不论是新的线程还是已经排队的线程都顺序使用锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
          ....
        }
        ...
    }  
}
public final boolean hasQueuedPredecessors() {  
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

tryAcquire都会检查CLH队列中是否仍有前驱的元素,如果仍然有那么继续等待,通过这种方式来保证先来先服务的原则。

那这样ReentrantLock如何实现可重入?是怎么重入的?

ReentrantLock如何实现可重入?

其实也很简单,在获取锁后,设置一个标识变量为当前线程exclusiveOwnerThread,当线程再次进入判断exclusiveOwnerThread变量是否等于本线程来判断.

protected final boolean tryAcquire(int acquires) {
  
    // 状态变量的值为0,说明暂时还没有线程占有锁
    if (c == 0) {
     	if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 当前线程获取了锁,并将本线程设置到exclusiveOwnerThread变量中,
            //供后续自己可重入获取锁作准备
            setExclusiveOwnerThread(current);
            return true;
        }
    } //之所以说是重入锁,就是因为在获取锁失败的情况下,还会再次判断是否当前线程已经持有锁了
    else if (current == getExclusiveOwnerThread()) {
        ...
    }
   
}

当看完公平锁获取锁的流程,那其实我们也了解非公平锁获取锁,那我们来看看。

ReentrantLock公平锁模式与非公平锁获取锁的区别?

其实非公平锁获取锁获取区别主要在于:

其他功能都类似。在理解了获取锁下,我们更好理解ReentrantLock::unlock()锁的释放,也比较简单。

ReentrantLock::unlock()释放锁,如何唤醒等待队列中的线程?

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
    compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;//这里的s是头节点(现在是头节点持有锁)的下一个节点,也就是期望唤醒的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
        		s = t;
    }
    if (s != null)
    	LockSupport.unpark(s.thread); //唤醒s代表的线程
}

综合上面的ReentrantLock的可重入,可实现公平\非公平锁的特性外,还具有哪些特性?

ReentrantLock除了可重入还有哪些特性?

ReentrantLock与Synchrionized的区别

ReentrantLock使用场景

上述就是小编为大家分享的怎么深入理解ReentrantLock原理了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

推荐阅读:
  1. ReentrantLock的实现原理是什么
  2. 深入koa-bodyparser原理解析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

reentrantlock

上一篇:怎么实现Spark2.x BlockManager原理剖析

下一篇:基于CRF的命名实体识别系统原理及实例剖析是怎样的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》