ReentrantLock 公平锁源码 第2篇

打印 上一主题 下一主题

主题 904|帖子 904|积分 2712

Reentrant 2

前两篇写完了后我自己研究了下,还有有很多疑惑和问题,这篇就继续以自问自答的方式写
如果没看过第1篇的可以先看看那个https://www.cnblogs.com/sunankang/p/16458795.html
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) &&
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4.         selfInterrupt();
  5. }
复制代码
进入acquireQueued方法
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         //这个属性的作用是啥???
  5.         boolean interrupted = false;
  6.         for (;;) {
  7.             final Node p = node.predecessor();
  8.             if (p == head && tryAcquire(arg)) {
  9.                 setHead(node);
  10.                 p.next = null; // help GC
  11.                 failed = false;
  12.                 return interrupted;
  13.             }
  14.             if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node);
  20.     }
  21. }
复制代码
第一个问题
interrupted这个变量的作用
  1. private final boolean parkAndCheckInterrupt() {
  2.     LockSupport.park(this);
  3.     return Thread.interrupted();
  4. }
复制代码
在parkAndCheckInterrupt方法中最后return的是这个线程是否被打断,它的作用是啥?
先来回顾interrupt(),interrupted() 和isInterrupted()三者区别,长得很像,注意区分
interrupt()的作用是中断线程,如果被中断的线程处于阻塞状态下,例如调用wait(),join() sleep(),则抛出异常,否则只是设置一个中断标记为true,注意:仅仅是设置中断状态为true,并不会去 "中断" 线程
interrupted() 获取线程的中断状态并且清空中断状态(将中断状态设置为false)
isInterrupted() 获取线程的中断状态并不会清除中断状态
调用 interrupt 会使park方法立即结束,可以理解为唤醒
继续代码,看这个变量最后到了哪里
情况1 没有被打断过

假设线程没有被中断过,那么parkAndCheckInterrupt返回就是false
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor();
  7.             if (p == head && tryAcquire(arg)) {
  8.                 setHead(node);
  9.                 p.next = null; // help GC
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  14.                 interrupted = true;
  15.         }
  16.     } finally {
  17.         if (failed)
  18.             cancelAcquire(node);
  19.     }
  20. }
复制代码
那么不进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())这个if,获取到锁后返回false,回到acquire方法
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  3.         selfInterrupt();
  4. }
复制代码
因为false,所以不进入selfInterrupt(),方法结束
情况2 park或准备park,被唤醒后直接获取到了锁

先证明一下打断是会唤醒park中的线程的

我就再重复粘一下代码了,方便看
  1. private final boolean parkAndCheckInterrupt() {
  2.     LockSupport.park(this);
  3.     return Thread.interrupted();
  4. }
复制代码
那么返回的就是true,回到上级acquireQueued方法
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor();
  7.             if (p == head && tryAcquire(arg)) {
  8.                 setHead(node);
  9.                 p.next = null; // help GC
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             //返回到这里
  14.             if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node);
  20.     }
  21. }
复制代码
因为返回true,所以进入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 将interrupted返回true
假设循环获取到锁,那么再返回上一级acquire()
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  3.         selfInterrupt();
  4. }
复制代码
那么进入selfInterrupt()
  1. static void selfInterrupt() {
  2.     Thread.currentThread().interrupt();
  3. }
复制代码
是不是有点疑惑?我如果没有调用过interrupt() 那ReentrantLock就不做任何操作,我如果调用了,那它再给我调用一次 ????  还有情况3
情况3 park或准备park,被唤醒后没有获取到锁
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor();
  7.             if (p == head && tryAcquire(arg)) {
  8.                 setHead(node);
  9.                 p.next = null; // help GC
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             //假设在调用shouldParkAfterFailedAcquire成功后,马上就要调用parkAndCheckInterrupt 时间片用完了
  14.             if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node);
  20.     }
  21. }
复制代码
那么这个时候interrupted属性就有用了
首先要知道一点,一个被中断的线程是无法park的,除非清除了中断状态,即设置为将中断状态设置为false, 口说无凭,直接上图


第二张图还是在park状态,证明了被打断的线程是无法park的,除非将它中断状态设置为false
那么回到代码中就能知道这个的作用
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor();
  7.             if (p == head && tryAcquire(arg)) {
  8.                 setHead(node);
  9.                 p.next = null; // help GC
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             if (shouldParkAfterFailedAcquire(p, node) &&
  14.                 parkAndCheckInterrupt())
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node);
  20.     }
  21. }
复制代码
如果线程被打断唤醒,还是在for(;;)中,还是去获取锁,假设没有获取到呢?那么就一直在for循环中嘎嘎跑,因为线程的状态是被中断的,无法再次park了
  1. private final boolean parkAndCheckInterrupt() {
  2.     LockSupport.park(this);
  3.     return Thread.interrupted();
  4. }
复制代码
那么现在懂了最后的Thread.interrupted()作用了吗,就是将中断状态设置回false,好让线程没有获取到锁继续park
那这时候可能就问了:那你ReentrantLock把中断状态给我清空了,我自己如果有需要根据中断状态来判断的代码咋办啊?
好,咱们从park先被打断来捋一下
  1. private final boolean parkAndCheckInterrupt() {
  2.     LockSupport.park(this);
  3.     return Thread.interrupted();
  4. }
复制代码
因为被打断,线程醒来,执行Thread.interrupted()并清空中断状态,返回true
  1. final boolean acquireQueued(final Node node, int arg) {
  2.     boolean failed = true;
  3.     try {
  4.         boolean interrupted = false;
  5.         for (;;) {
  6.             final Node p = node.predecessor();
  7.             if (p == head && tryAcquire(arg)) {
  8.                 setHead(node);
  9.                 p.next = null; // help GC
  10.                 failed = false;
  11.                 return interrupted;
  12.             }
  13.             if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  14.                  //进入这里
  15.                 interrupted = true;
  16.         }
  17.     } finally {
  18.         if (failed)
  19.             cancelAcquire(node);
  20.     }
  21. }
复制代码
因为返回的是true,所以进入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())的代码块,将interrupted属性设置为true
那么for(;;)循环再来一次,如果没有获取到锁.继续park,直到被唤醒,走tryAcquire()获取到为止,那么此时interrupted变量就为true了
  1. public final void acquire(int arg) {
  2.     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  3.         selfInterrupt();
  4. }
复制代码
那么退出acquireQueued()方法回到acquire()中,因为acquireQueued()返回的是true,所以进入selfInterrupt()
  1. static void selfInterrupt() {
  2.     Thread.currentThread().interrupt();
  3. }
复制代码
所以懂了吗?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表