相关性质和条件变量-ReentrantLock详解(2)-AQS-并发编程(Java)
创始人
2024-02-19 05:32:32

文章目录

    • 1 可重入
    • 2 可打断
    • 3 公平锁
    • 4 条件变量
      • 4.1 await()
        • 4.1.1 主方法
        • 4.1.2 addConditionWaiter()
        • 4.1.3 isOnSyncQueue()
        • 4.1.4 checkInterruptWhileWaiting()
      • 4.2 signal()
        • 4.2.1 主方法
        • 4.2.2 doSignal()
        • 4.2.3 transferForSignal()
    • 5 后记

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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
}

在状态不为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不为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 GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

在加锁过程中如果竞争不到锁,会执行到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 GCfailed = false;return;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}

线程阻塞在队列时候,如果被打断,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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

公平锁:

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;
}

在尝试加锁的时候,如果锁状态计数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 orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}

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

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 cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

流程如下:

  • 如果线程被中断,直接抛中断异常
  • 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;elset.nextWaiter = node;lastWaiter = node;return node;
}

执行流程如下:

  • 获取队列尾节点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 queuereturn 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);
}

执行流程:

  • 判断如果节点状态为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;}

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

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);
}

流程如下:

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

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);
}

执行流程:

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

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;
}

执行流程如下:

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

5 后记

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

❓QQ:806797785

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

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...