• ReentrantLock加锁解锁过程


    公平锁

    调用lock方法加锁

    进入acquire方法获取加锁的许可

     进入tryacquire

     首先获取当前线程和status状态,status默认为0

    假如现在t1线程进入,然后t2线程进入(t2进入时t1还没有释放锁)

    if c==0成立,然后判断是否需要排队,调用hasqueuedpredecessors方法

     此时的头和尾都是null,此方法返回false,所以上面if(!hasqueuedpredecessors())成立,然后进行cas操作,将status改为1

    然后设置持有锁的是当前线程。最后返回true。即t1拿到锁继续执行自己的业务逻辑。。。。

    1、如果t2执行lock方法的时候t1已经释放锁,也就不会存在竞争,一次执行。

    2、如果t1还没有释放锁,t2也会走上面的代码。

    走到tryacquire方法尝试去获取锁的时候肯定失败,因为t1还在占用。方法返回false。

     然后会执行addwriter方法进入队列排队。

     首先创建一个节点node,节点里包含属性thread,pre,next,是一个双向链表

    由于tail=null,所以会走enq方法。

     此处无限循环,t==null也成立,首先会先初始化一个新的Node,node里的信息目前为空。然后设置为头部。(其实队列里的头部永远都是一个空节点,空节点的意思是有node对象,只不过里面的thread属性为空)

    继续循环,走else,里面的逻辑意思是将自己的节点设置为尾部,空节点设置为头部,然后将空节点的next指向t2节点,t2的pre指向头部的空节点。

    最终的关系如下图

    继续走acquirequeued方法

     1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     2         int ws = pred.waitStatus;//等待状态,默认为0,Node.SIGNAL=-1
     3         if (ws == Node.SIGNAL)
     4             /*
     5              * This node has already set status asking a release
     6              * to signal it, so it can safely park.
     7              */
     8             return true;
     9         if (ws > 0) {
    10             /*
    11              * Predecessor was cancelled. Skip over predecessors and
    12              * indicate retry.
    13              */
    14             do {
    15                 node.prev = pred = pred.prev;
    16             } while (pred.waitStatus > 0);
    17             pred.next = node;
    18         } else {
    19             /*
    20              * waitStatus must be 0 or PROPAGATE.  Indicate that we
    21              * need a signal, but don't park yet.  Caller will need to
    22              * retry to make sure it cannot acquire before parking.
    23              */
    24             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    25         }
    26         return false;
    27     }

    所以会走到compareAndSetWaitStatus方法将上一个节点的waitstatus改为-1,然后返回false,由于acquirequeued方法里是for(;;)

    所以会继续执行shouldParkAfterFailedAcquire,此时的waitstatus=-1,返回true。

    这个时候就是所谓的自旋,自旋了两次。

    返回true之后根据上面代码的逻辑会走parkandcheckinterrupt方法

     调用park方法,此时t2开始阻塞。知道t1释放锁。

    此时t3如果也进入lock方法。

    如果t1还没有释放锁, 和t2同样的逻辑。直到t3也阻塞。

    如果此时t1已经释放锁,那么会是t2拿到锁。因为入队之后,会尝试一次加锁许可的过程中,会判断当前节点是不是第一个排队的节点(也就是他的上一个节点是不是头部)

    如果是才有资格去获取锁,因为前面还有t2,所以t3会继续排队,t2被唤醒之后,会将自己节点的thread属性赋值null,next指向t3,t3的pre指向t2。

    然后把头部指向t2,尾部指向t3。空节点的next=null。

    此时最先初始化的那个空节点已经没有任何引用。等待被回收。源码里有体现。

    非公平锁

     调用lock方法,直接就就行cas操作。

    unlock解锁过程

     

     简单几步:1、将status设置为0

    2、设置持有当前锁的线程为null

    3、唤醒队列里waitstatus不等于0的节点,调用unpark方法。

     简单总结:调用lock方法,存在竞争的时候,T2会去入队,首先会初始化一个空节点,t2节点实际上存放的是第二个位置,t3进来的时候继续在后面排队,

     t2和t3都是调用park方法进行阻塞。入队的时候会将前面的节点的waitstatus状态由0改为-1。在调用unlock的时候会将waitstatus不等于0的释放。

  • 相关阅读:
    PB中的Grid视图
    MVC加jquery的无刷新列表分页摘要
    Quartz.Net 1.30的一些设置说明
    将Excel的数据库字典导到PDM中
    HubbleDotNet使用备忘
    EntLib5.0 日志应用程序块(logging) 使用与配置
    生成随机密码
    网站整合QQ登录
    PB代码参考段
    SQL查询之 Pivot 详解
  • 原文地址:https://www.cnblogs.com/hkdpp/p/11917383.html
Copyright © 2020-2023  润新知