ReentrantLock原理

一、 ReentrantLock原理开篇

ReentrantLock(可重入锁)在平时并发编程时用的很多,基于它实现的独占锁、共享锁功能强大,内部还实现了公平锁、非公平锁、满足了可重入性等等,那么这么强大的锁同步机制,它的内部又是怎么构造实现的呢?我们先从它的类图分析
在这里插入图片描述

二、AQS

2.1 类图分析

从类图中我们可以看到,ReentrantLock的实现似乎是傍上了“大佬”,站在了AbstractQueuedSynchronizer这个类的“肩膀上”,那么这个所谓的AQS又是什么来头?

2.2 AQS分析

2.2.1 概述

AbstractQueueSysnhronizer:阻塞式锁和相关的同步器工具的框架

2.2.2 特点
  • 用state属性来表示资源的状态(分独占式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

    • getState 获取state 状态
    • seteState 设置state 状态
    • compareAndSetState - cas 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于FIFO的等待队列,类似于Monitor 的 EntryList
    在这里插入图片描述

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor 的 WaitSet

AQS中的核心变量:

// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;

// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
private transient volatile Node tail;

// 这个是最重要的,代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
// 这个值可以大于 1,是因为锁可以重入,每次重入都加上 1
private volatile int state;

// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer

子类主要实现这样一些方法(默认抛出UnSpupportedOperationException)

tryAcquire(int);        // 尝试获取独占锁,可获取返回true,否则false

tryRelease(int);        // 尝试释放独占锁,可释放返回true,否则false

tryAcquireShared(int);  // 尝试以共享方式获取锁,失败返回负数,只能获取一次返回0,否则返回个数

tryReleaseShared(int);  // 尝试释放共享锁,可获取返回true,否则false

isHeldExclusively();    // 判断线程是否独占资源
2.2.3 对AQS理解

AQS作为阻塞式锁和相关同步器工具的框架,提供了强大的锁实现功能,基于AQS我们可以方便的开发出一套个性化的锁同步机制。而ReentrantLock的实现就主要依赖了AQS所提供的state状态变量以及内部实现的双向链表的机制,以CAS的方式为我们提供了一套完备的锁机制。

三、ReentrantLock原理

3.1 非公平锁实现

1)默认是非公平锁
 public ReentrantLock() {
        sync = new NonfairSync();
    }
//NonfairSync继承于AQS
2)加锁流程
    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
            if (compareAndSetState(0, 1))//修改aqs状态
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //有竞争时进入acquire
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
3)锁竞争情况(失败)

3.1)无竞争时:

在这里插入图片描述

  • 设置state为1
  • 并设置exclusiveOwnerThread为当前线程

3.2)第一个竞争出现时:

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
//acquireQueued(addWaiter(Node.EXCLUSIVE)尝试创建节点对象,放入等待队列中去

在这里插入图片描述

Thread-1竞争线程执行步骤:

  1. CAS尝试将state由0修改为1,结果失败,进入acquire()方法

  2. 由if判断进入tryAcquire逻辑,这时state已经是1,结果依然失败

  3. 接下来进入addwaiter逻辑,构造Node队列

    //构造Node队列
     private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
    
    • 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态
    • Node节点的创建是懒惰的
    • 其中第一个Node被称为Dummy(哑元)或哨兵,用来占位,并不关联线程

在这里插入图片描述

  1. 当前节点进入 acquireQueued逻辑
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //死循环不断尝试获取锁
            for (;;) {
                //获取node的前驱节点
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • acquireQueued会在一个死循环中不断尝试获取锁,失败后进入park阻塞
  • 如果自己是紧邻着head(排第二位),那么再次tryAcquire尝试获取锁,当然这时state仍为1,失败
  1. 进入 shouldParkAfterFailedAcquire 逻辑
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)  //Node.SIGNAL=-1
            //前驱已是-1,直接返回true
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //waitStatus改为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
  • 将前驱node,即head的waitStatus改为-1(-1表示有责任唤醒后继节点),这次返回false

    在这里插入图片描述

  • shouldParkAfterFailedAcquire 逻辑执行完毕回到for循环重新执行,这次shouldParkAfterFailedAcquire 返回的是true(因为前驱节点已为-1),执行&&后半部分

  • 进入parkAndCheckInterrupt逻辑,将线程打断(图中以灰色表示)

 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

在这里插入图片描述

3.3)多个线程竞争失败后

在这里插入图片描述

  • 此时多个竞争失败线程形成双向链表,并被park,交由前驱节点唤醒
4)锁释放流程
 public final boolean release(int arg) {
        if (tryRelease(arg)) { //尝试释放
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);  //唤醒最近的后继节点
            return true;
        }
        return false;
    }
  1. Thread-0释放锁,进入tryRelease流程,如果成功

    • 设置exclusiveOwnerThread为null
    • state设置为0

    在这里插入图片描述

    • 如果当前节点不为null,且head的waitStatus的值不是0,说明有后继节点,进入unparkSuccessor逻辑
    private void unparkSuccessor(Node node) {
          
            int ws = node.waitStatus;
            if (ws < 0)
                //ws是-1,重置为0
                compareAndSetWaitStatus(node, ws, 0);
            Node s = node.next;
            //没有后继的保底策略
            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;
            }
            //有后继的unpark唤醒策略
            if (s != null)
                LockSupport.unpark(s.thread);
        }
    
    
5)阻塞线程唤醒

因为是非公平锁,所以存在被抢占锁的情况

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //死循环不断尝试获取锁
            for (;;) {
                //获取node的前驱节点
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//此处被唤醒
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

5.1)此时没有新来线程竞争锁

  1. 后继被唤醒的线程在之前的park位置唤醒后再次执行for循环,获取锁成功,进入if代码块
  2. 设置当前节点为头结点,并断开初始的头节点

在这里插入图片描述

5.2)此时有新来线程竞争锁

可能会竞争失败,重新进入阻塞

3.2 可重入原理

1)可重入加锁
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { 
                //尝试cas获取锁,这里体现了非公平性:不去检查AQS队列
                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;
        }
    }
  • state为0,尝试加锁
  • state不为0,判断当前加锁线程是否是获取锁线程,是则将state加1即可,否则返回false
2)可重入解锁
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
  • state不断减,到0,则释放锁

3.3 可打断模式

1)不可打断模式
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;  //返回打断标记
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true; //能进入if说明该线程被打断了,设置打断标记为true,这样即使获取锁它依然具有打断标记
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
     private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //清除打断标记(防止被打断后导致线程无法被park),如果被打断过,返回true,否则返回false,
        return Thread.interrupted();
    }

简单来说,就是让在阻塞时的线程不会在获取锁之前被打断,而是在获取锁之后带上打断标记运行

2)打断模式
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException(); //与上述代码的唯一区别,这里抛异常,直接在阻塞时就被打断
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3.4 公平锁实现

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //该if就是公平锁和非公平锁最大区别
                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;
        }
    }
//如果准备加锁的线程不是头结点下一个节点的线程,无法加锁,保证了加锁的公平性
   public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
       //h!=t说明链表中有节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

3.5 条件变量实现

1)await

每个条件变量其实就是对应着一个等待队列,其实现类是ConditionObject

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            long savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

进入addConditionWaiter逻辑,创建的也是一个双向链表

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) { //Node.CONDITION=-2
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

在这里插入图片描述
为什么是调用fullRelease呢?

因为拿到锁的线程可能会发生重入现象,通过fullRelease完全释放锁
在这里插入图片描述
unpark AQS队列中的下一个节点,竞争锁,加锁没有其他竞争加锁,那么Thread-1竞争成功
在这里插入图片描述

2)signal
public final void signal() {
            //是否是持有锁的
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
//脱离ConditionObject的等待队列
private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
//转移到等待队列对尾
final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

在这里插入图片描述

执行的transferForSignal逻辑,将该Node加入到AQS队尾,将Thread-0的WaitStatus改为0,Thread-3的waitStatus改为-1
在这里插入图片描述

四、总结

理解AQS中基于内存共享的变量模型(最根本是volatile的原理应用),搭配合理的数据结构(双向链表),就能够理解我们AQS工具类的根本。在这个根本上合理组织和构建,ReentrantLock也就诞生了,这种基于工具类构建增强类,在共有基础上衍生出更丰富和更独特的功能的方式,在面向接口编程中比比皆是,需要我们好好体会!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值