博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入浅出多线程——ReentrantLock (二)
阅读量:5102 次
发布时间:2019-06-13

本文共 5829 字,大约阅读时间需要 19 分钟。

  

   文章中介绍了该类的基本使用,以及在源码的角度分析lock()、unlock()方法。这次打算在此基础上介绍另一个极为重要的方法newCondition(),其实这类已经不属于ReentrantLock的范畴了,是java.util.concurrent.locks.Condition接口的一个实现,位于AbstractQueuedSynchronizer(简称:AQS)中的内部类ConditionObject。

 

  该类提供了await*()、signal*()等方法。本次只对await()、signal()方法在源码的角度进行解析。

原理分析

await()方法分析

ConditionObject.await()
1         public final void await() throws InterruptedException { 2             if (Thread.interrupted()) 3                 throw new InterruptedException(); 4             Node node = addConditionWaiter(); 5             int savedState = fullyRelease(node); 6             int interruptMode = 0; 7             while (!isOnSyncQueue(node)) { 8                 LockSupport.park(this); 9                 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)10                     break;11             }12             if (acquireQueued(node, savedState) && interruptMode != THROW_IE)13                 interruptMode = REINTERRUPT;14             if (node.nextWaiter != null) // clean up if cancelled15                 unlinkCancelledWaiters();16             if (interruptMode != 0)17                 reportInterruptAfterWait(interruptMode);18         }

  1、判断线程是否被中断,如果被中断则抛出 InterruptedException。

  2、调用了addConditionWaiter()方法,将当前线程添加到等待队列中。

  3、第5行,调用fullyRelease(Node)方法,尝试释放当前线程并返回释放前的state值。

  4、第7行,while循环条件为isOnSyncQueue(Node) 取反,也就是说该方法必须返回false才能进入循环体。进入后调用LockSupport.park()挂起当前线程。

  5、等待调用signal()方法,将其加入同步队列等待调度到。调度到后,线程接着往下走,因为此时已经在同步队列中,while循环跳出。

  6、来到第12行,尝试将state的值还原到await之前,如果还原成功,则线程继续往下走。如果不成功说明再此期间,已经被其他线程占用,则继续等待。

  7、如果当前等待的节点有下游等待节点,在进行清理被取消的等待节点。

  8、方法执行完毕后,则继续执行线程的业务,直至调用到unlock()。

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

  1、判断lastWaiter是否为有效状态,如果无效,执行unlinkCancelledWaiters()方法,将其无效的节点清理掉。将当前线程设置为一个node,waitStatus值为-2。

  2、判断lastWaiter是否为null,如果为null代表队列为空,那么将创建的node赋值到队列的firstWaiter属性上,如果不为null,则链接到队列最后一个node的下游(因为第一次调用await()方法,此时lastWaiter肯定为空)。然后将队列的lastWaiter属性设置为新建的node。

Condition.fullyRelease(Node)
final int fullyRelease(Node node) {        boolean failed = true;        try {            int savedState = getState();            if (release(savedState)) {                failed = false;                return savedState;            } else {                throw new IllegalMonitorStateException();            }        } finally {            if (failed)                node.waitStatus = Node.CANCELLED;        }    }

  1、获取当前线程的state值、然后调用AQS.release(int)尝试释放当前线程,如果释放成功则返回线程state。

有关AQS.relase(int)方法的分析,已经在前一篇文章中进行详细说明。如需查看请点击

  2、如果没有释放成功,则抛出异常 IllegalMonitorStateException,并且将node.waitStatus状态设置为取消。

AbstractQueuedSynchronizer.isOnSyncQueue(Node)
1     final boolean isOnSyncQueue(Node node) {2         if (node.waitStatus == Node.CONDITION || node.prev == null)3             return false;4         if (node.next != null) // If has successor, it must be on queue5             return true;6 7         return findNodeFromTail(node);8     }

  这个方法从字面意思为当前节点是否在同步列队中,如果在则返回true。这个地方个人表示挺难理解的,在这里我尽量用通俗易懂的方式进行阐述。

  1、第2行,判断当前node的waitStatus值是否为-2(await())或者node.prev是否为null,两者满足其一就返回false。判断node的waitStatus的值是否为-2很好理解,调用了await后,第一次来到这个方法,肯定是成立的。判断node.prev是否为null,这个地方是比价绕的,第一次进来同样为null。在什么时候这个条件不成立呢?当时看的时候就有点头晕,于是就开启联想模式,终于有了点思路,就是说调用await()方法的线程一定处于同步列队的head,此时他的prev一定是null,在看过signal()方法后,看到线程被其唤醒时需要重新加入同步队列。这时只能放到队列的末尾,node.prev就被指向了他的上游节点。

  2,当第一个判断全部不成立时,接着执行了第二个判断,node.next是否为null,不为null则返回true。这个地方是他已经处于了同步队列,并且已经有了下游节点。

  3,前两个判断都不满足的情况下直接调用了findNodeFromTail(node),字面意思是从队列的末尾查找node,什么情况下会调用到这个方法呢?node本身就处于末尾时调用。

 signal()方法分析

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

   获取到第一个等待者,如果不为null则执行doSignal(Node)

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

   1、进入do-while循环体,判断first.nextWaiter是否为null,如果为null则将lastWaiter置为null。

   2、紧接着进入while条件,继续循环的条件为调用transferForSignal(Node)返回false,并且firstWaiter不为null。

AbstractQueuedSynchronizer.transferForSignal(Node)
1     final boolean transferForSignal(Node node) { 2  3         if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 4             return false; 5  6         Node p = enq(node); 7         int ws = p.waitStatus; 8         if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 9             LockSupport.unpark(node.thread);10         return true;11     }

  1、首先将当前node的waitStatus的值由-2设置为0,并判断是否返回false。如果返回false,则说明该线程被取消。

  2、调用熟悉的enq(Node)方法,把当前node拼接到同步列队中并返回node上游节点p。

  3、此时p的waitStatus等于0。所以直接进入第二个判断条件,将p的waitStatus从0设置为-1。如果此时设置失败后,将直接当前node解锁。设置失败的前提个人理解为:p处于运行中,也就是说调用了LockSupport.unpark(p.thread),还有一种情况就是线程被取消。

 总结

   1、Condition提供了一套线程等待及唤醒机制,与之匹配为Object.wait/notify等方法。但后者的使用条件为synchronized,不能直接在ReentrantLock中应用。

   2、Condition可以在一个lock对象中存在多个,灵活方便。

   3、ConditionObject类中也存在了大量的AQS操作,同样说明AQS是同步框架的基础框架。

 

转载于:https://www.cnblogs.com/itunic/p/java-reentrant-lock2.html

你可能感兴趣的文章
使用word发布博客
查看>>
面向对象的小demo
查看>>
微服务之初了解(一)
查看>>
GDOI DAY1游记
查看>>
收集WebDriver的执行命令和参数信息
查看>>
数据结构与算法(三)-线性表之静态链表
查看>>
mac下的mysql报错:ERROR 1045(28000)和ERROR 2002 (HY000)的解决办法
查看>>
Hmailserver搭建邮件服务器
查看>>
django之多表查询-2
查看>>
快速幂
查看>>
改善C#公共程序类库质量的10种方法
查看>>
AIO 开始不定时的抛异常: java.io.IOException: 指定的网络名不再可用
查看>>
MyBaits动态sql语句
查看>>
HDU4405(期望DP)
查看>>
拉格朗日乘子法 那些年学过的高数
查看>>
vs code 的便捷使用
查看>>
Spring MVC @ResponseBody返回中文字符串乱码问题
查看>>
用户空间与内核空间,进程上下文与中断上下文[总结]
查看>>
JS 中的跨域请求
查看>>
JAVA开发环境搭建
查看>>