• 相关性质和条件变量-ReentrantLock详解(2)-AQS-并发编程(Java)


    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在状态不为0即被占用时,判断锁持有线程是不是当前线程,如果是的话表示锁重入,状态计数+1。

    在释放锁体现代码如下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果状态计数-1不为0说明有锁重入,重新设置锁状态计数,而不是直接把锁持有线程设置为null。

    2 可打断

    ReentrantLock默认实现是非公平锁,为不可打断模式,代码在之前的分析加锁和解锁的时候有提及,这里在详细说明下。

    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;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在加锁过程中如果竞争不到锁,会执行到parkAndCheckInterrupt()方法的时候同park()方法阻塞。在阻塞的过程有如果被打断,parkAndCheckInterrupt()返回true,interrupted打断标记置为true。该线程绑定的节点仍然存在与队列中,除非竞争到锁的时候,也仅仅是返回interrupted值true,然后才会执行自我打断,这种称为不可打断模式。

    下面我们来看下可打断是如何实现的?

    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);
        }
    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    线程阻塞在队列时候,如果被打断,parkAndCheckInterrupt()返回true,doAcquireInterruptibly()方法直接抛出异常InterruptedException,这种称为可打断模式。

    3 公平锁

    如果不清楚公平锁和非公平锁概念,可以查看上一篇文章或者自行查阅相关文档。默认实现是非公平锁,这里我们对比分析下,它是如果保证“公平",在加锁过程中体现的。

    非公平锁:

    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) {
                    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;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    公平锁:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在尝试加锁的时候,如果锁状态计数state==0

    • 非公平锁:通过cas方式把state由0置为1,不会检测队列中是否有其他等待的线程。
    • 公平锁:优先检测队列中是否有阻塞等待的其他线程

    具体检测方法hasQueuedPredecessors()代码如下:

    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;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    竞争锁的前提是队列为空或者队列不为空且队列第二个节点绑定的线程不是当前现场,这就公平锁的体现。

    4 条件变量

    条件变量维护的是一个单向链表的队列,下面我们主要讲解下调用await()在队列等待和当条件满足是signal()唤醒。

    4.1 await()

    示意图如下4.1-1所示:

    在这里插入图片描述

    分析如下:

    4.1.1 主方法

    源代码如下:

    public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        Node node = addConditionWaiter();
        int 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    流程如下:

    • 如果线程被中断,直接抛中断异常
    • addConditionWaiter()方法加入队列末尾,返回新创建的节点
    • fullyRelease:释放锁,考虑有锁重入的可能,返回状态
    • 循环判断!isOnSyncQueue()表示该节点是否不在竞争锁的队列中
      • LockSupport.park()阻塞在该条件变量队列,除非被sinal(),sinalAll()或者被中断
      • 被唤醒或者中断情况下,判断是否是被中断,如果是跳出循环
    • acquireQueued()尝试获取锁,否则阻塞在竞争锁队列。如果被打断判断打断模式
    • 如果node.nextWaiter != null,清理已取消的节点
    • 如果打断模式不是0即有被打断,根据打断模式作相应处理
    4.1.2 addConditionWaiter()

    目标就是插入条件队列尾部。

    源代码如下:

    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    执行流程如下:

    • 获取队列尾节点t
    • 判断如果不为空且t的等待状态不是Node.CONDITION
      • 清理已取消的节点
    • 已当前线程,Node.CONDITION为参数创建新的节点
      • 也就是说条件队列上节点除非是被取消的其他状态一律是Node.CONDITION
    • 下面执行把新节点链入尾部操作,不在详述
    4.1.3 isOnSyncQueue()

    目标判断节点是否在竞争锁的队列中。

    源代码:

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    执行流程:

    • 判断如果节点状态为Node.CONDITION或者node.pre==null
      • Node.CONDITION只有在新建加入条件队列的节点,所以肯定不在竞争锁的队列
      • node.pre==null 排除哨兵节点,竞争锁队列的节点前驱不能为空
      • 返回false
    • 判断node.next != null,如果有后继节点且在不满足上面条件下,肯定在竞争锁队列中
      • 返回true
    • 最后从竞争锁队列尾部变量队列检查是否在队列中
    4.1.4 checkInterruptWhileWaiting()

    主要目的是检查什么原因导致阻塞被疏通的,如果是被中断,直接跳出循环。源代码如下:

    private int checkInterruptWhileWaiting(Node node) {
        return Thread.interrupted() ?
            (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
            0;
    }
    final boolean transferAfterCancelledWait(Node node) {
            if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
                enq(node);
                return true;
            }
            /*
             * If we lost out to a signal(), then we can't proceed
             * until it finishes its enq().  Cancelling during an
             * incomplete transfer is both rare and transient, so just
             * spin.
             */
            while (!isOnSyncQueue(node))
                Thread.yield();
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果被中断,还是会转移该节点到竞争锁队列中,

    4.2 signal()

    唤醒示意图如下4.2-1所示:

    在这里插入图片描述

    分析如下:

    4.2.1 主方法

    源代码如下:

    public final void signal() {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    流程如下:

    • 如果锁持有线程非当前线程,直接跑一次
    • 条件变量队列不为空,唤醒第一个等待的节点。
    4.2.2 doSignal()

    源代码如下:

    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行流程:

    • 循环执行
      • 头节点指针指向下一个节点
      • 把要唤醒的节点转移至锁竞争队列
        • 失败且下一个节点不为空
    4.2.3 transferForSignal()

    源代码如下:

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
    
        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行流程如下:

    • cas把节点状态由Node.CONDITION设置为0,失败返回false
    • enq()把节点放入锁竞争队列尾部,具体操作可查看之前ReentrantLock加锁流程分析,返回前驱节点p
    • 判断如果p的等待状态>0(取消)或者cas设置p的等待状态由当前状态为Node.SIGNA失败,直接unpark唤醒节点上绑定的线程
    • 放回true

    5 后记

    如有问题,欢迎交流讨论。

    ❓QQ:806797785

    ⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent

  • 相关阅读:
    一台服务器通过nginx安装多个web应用
    C++变量声明和变量定义
    虚拟机网络配置
    android源码添加c/c++工程
    Redis速学
    本地CMD命令将webp格式文件批量转换为图片格式(jpg、png、bmp、gif)
    中秋节——嫦娥奔月
    Mybatis 注解开发 + 动态SQL
    Java版本spring cloud + spring boot企业电子招投标系统源代码
    [架构之路-235]:目标系统 - 纵向分层 - 数据库 - 数据库系统基础与概述:数据库定义、核心概念、系统组成
  • 原文地址:https://blog.csdn.net/gaogzhen/article/details/128060832