zoukankan      html  css  js  c++  java
  • Lock

    Lock

             ReentrantLock : 抢占锁失败 , 会进入同步队列 , 此队列是双向链表。 (prev --next)

              Condition : 只有抢占到锁的线程, 才可以调用 await()方法, 会释放CPU资源 , 释放锁 , 进入到Condition队列。单向链表 。(nestWaiter

              

              J.U.C

     a . 可以看到这个类存在于 rt.jar 中, java.util.concurrent 目录下。

     a . 今天主要说的就是 ReentrantLock 这个实现类.。所有实现了Lock接口的类中, 除了ReentrantLock,其余的实现都是内部类。

    开始根据流程开查看源码 :

    1.  lock.lock()方法

     

     

    a . 这里看到了 ,lock分为公平锁和非公平锁 ,其实这里我们就可以才到,公平锁还是非公平锁,是通过构造方法来设置的。 默认非公平锁。 new NonfairSync()。

    b . 传入boolean fair , 当为 true 的时候, 会以公平锁来处理。非公平锁的效率更高一点。

    c . 我们这里按照非公平锁来进行源码分析.

     

     a . if (抢到锁) ,这里采用的cas乐观锁来替换 state 的值, 默认为 Int类型 即是 0 . 如果当前内存内还是0 ,就更新为1 。 更新为1 成功,表示还没有现成更新成功过。

          这里 >0 表示当前锁被占用,之所以不一定是0 ,是因为 reentractLock 和 synchronized 都支持重入锁,当再次去获取锁的线程 == 当前持有锁的线程 , 代表重入,只需要把state+1即可.

    b .   setExclusiveOwnerThread(Thread.currentThread());  将当前线程这是为独占线程.

    c . else 就是 cas 失败, 即未争抢到锁, 调用acquire(1) ,开始执行接下来的流程.

     a. tryAcquire(arg)  ,再次去尝试获取锁, (之所以说是非公平锁,就是每一个线程刚进来,都可以去抢一下锁,而不是先看队列中有没有线程在等待获取锁. 这也是公平锁和非公平锁的区别)。

         查看tryAcquire(arg) 实现类 。

     a . 这是去尝试获取锁的代码..先判断当前state 是否为0 ,如果为0 ,表示当前锁空闲的。 如果cas成功,表示锁抢占成功。同样把当前线程设置为独占线程。return true (抢占锁成功)

     b. current == getExclusiveOwnerThread() , 当前线程与独占线程是同一个, 这就是重入. state+1 并将结果set为State的值。return true (抢占锁成功)

     c . return false 表示再次尝试获取锁失败 。

     d . 当 再次获取锁结果为false的时候,  开始运行 addWaiter(Node.EXCLUSIVE) 这行代码 。 其实 Node.EXCLUSIVE = null 。

     

     

     a . 这里就是把当前没获取到锁的线程封装为Node节点。Node的属性 (volatile Thread thread) 设置为当前线程 。 此时waitStatus(volatile int waitStatus),因为未设置值,且为Int类型,此时就是默认值 0 。 waitStatus 可能出现的值就是上面的截图 。 ( 1 , -1 , -2 ,-3 )。这的同步队列就是要把每个Node节点的waitStatus节点设置为SIGNAL(-1)。

     b. 第一次进来,pred == null , 所以可以直接进入enq(node)内. Node node = new Node(Thread.currentThread(), mode); 线程为当前线程, mode为null.

     a . 如果不是第一次常见链表, 那么此时tail节点就不为Null .  那么就会进入 if(pred != null) 条件内。同样的道理, 将当前线程的Node.prev 设置为链表的tail。 同时使用cas乐观锁,将当前线程的Node节点设置为链表的tail节点。(总体理解为就是把当前线程的Node。 拼接到链表的末端), 如果成功 ,返回当前线程的Node, 否则代码继续往下流转。

     b . 进入 enq(node) ;

     a .  自旋 + cas

     b .  可以看到, 当不存在链表的时候,  这里会将 head 赋值为一个 new Node(); thread = null . (无论何时,Head指向的node节点,Thread属性都为Null)

           其实这个 new Node() 就相当于被当做了目前持有锁的线程. new Node() 的 next 指向 当前线程的Node . 当前线程的Node 的 prev 指向 new Node().

    c.  可以很明显看到的是 , 这里每次创建前Node 与 后面Node之间的联系的时候,都是先创建Prev的链接。 CAS后,才会处理 Next的链接。 所以之后出现的场景。 就是从后往前来判断一些Node节点。 就是因为 Prev 链接是先关联上的。

    d . enq(Node node) 返回值为 当前线程Node 的前一个Node ,即 prev。

    e . addWaiter(Node mode)返回值Node为当前线程的Node。

     a . 开始看这个方法 acquireQueued(addWaiter(Node.EXCLUSIVE), 1))  , 这个方法的第一个参数是 当前线程的 Node , 第二个参数为 1 。

     

     备注 : 首先需要知道的是 ,这里的for循环,在当前线程未获取到锁的时候, 是不会停止的。唯一的return出口, 是获取到锁,并把自己设置为Head节点的时候。

    a . final Node p = node.predecessor(); 获取当前线程Node的前一个节点.

    b . 如果P是头结点(Head) , 那就再次尝试获取锁 , 如果获取成功,说明上一Node节点也刚好释放锁,这里就再获取锁(因为不是公平锁,所以其实这里也不一定就能拿到锁)

    c . 如果获取成功,将当前线程的Node设置为Header. 同时断开双向链表之间的 prev 和 next 。failed 变量赋值为 false , 表示 非失败 = 已经获取锁成功 。

    d. 第二个if. 因为每一个新增的Node ,都没有设置过waitStatus.所以这一定是 0 (int有默认值0).

    e . ws > 0 .只能是cancle状态.  node.prev = pred = pred.prev;这里就是2次赋值, 把 waitstatus = cancle的Node节点删除。

         可以看到,在ws > 0 这个if内,是一个do while 循环。 也就是说这里会一直往前面找,把所有为cancle属性的Node节点都删除掉。

    f.  最后就是将当前 节点的前一个节点, Node的 waitStatus属性设置为 Signal 。

     

     a . 截止到现在就确定已经获取不到锁了,只能调用LockSupport.part(this), 将此线程挂起了,后面代码将不再执行。等待unpark。

     b . 当在unpark后, 这个for循环只有当 当前前程的Node前一个节点为head, 并且抢到锁才会有return的出口。

    释放锁

     a . lock.unlock(); 释放锁

     

     a . 调用 sync.release(1) .

     

     a . 先看tryRelease(1)方法.

     

     a . 因为存在重入锁, 但一次只释放一个.所以这并不能将state直接置为0。

     b . 只能当前独占线程(独占线程=表示是这个线程获取到的锁)才能来释放自己的锁,如果不 == , 跑出 throw new IllegalMonitorStateException()。

     c . 如果不存在复用, 那么减一次, 就直接为0了.  返回true, 并将独占锁设置为 Null .否则就是存在复用, 将state 设置为最新 - 1之后的值,等待再次释放. 并返回false.(其它重入锁的地方代码执行结束后,也会调用自己的unlock, 再次来 -1 , 直至减为0)。

     d . 假设当前锁不存在重入, 这里返回为false, 代码继续往下流转。

     a . 我们知道 其实每一个Node节点, 都会在下一个Node节点进来的时候, 更新为SIGNAL。 只有一个节点为SIGNAL的时候,当它作为头结点Head,才会唤醒下一个Node 。 static final int SIGNAL = -1 。 所以这里  !=  0 。

     a . 如果当前状态 < 0 (上面的说法,这正常状态下是 -1), 采用cas乐观锁, 将waitStatus 改为 0 . 

     b . 接下来就可以开始处理head节点的next节点了。Node s = node.next 。 如果头结点的下一个节点 s 为空 或者 s.waitStatus > 0 (waitStatus = 1 = cancle状态)。进入if。

     b . 开始循环查找,如果不为空 并且 不为cancle.那么就将找到的这个Node 重新赋值为 s。

     备注 : for循环的意思 是 从后往前开始找Node. (其实就从后往前找到一个离当前线程Node最近不为空 且 不为cancle状态的Node,然后唤醒)。

                  这其实我们就看到了, 我们是从后往前开始找Node.

    e . 开始调用 LockSupport.unpark(s。thread) , 将挂起的线程释放。Lock方法中未执行的代码将继续执行。如果这期间没人抢占锁。这个被释放的线程基本山就可以顺利获取锁了。

     LockSupport.park(tread) 和 LockSupport.unpark(thread)。

    人总得做点什么 ,不是么
  • 相关阅读:
    进制
    流程控制
    运算符
    格式化输出
    数据结构-树的遍历
    A1004 Counting Leaves (30分)
    A1106 Lowest Price in Supply Chain (25分)
    A1094 The Largest Generation (25分)
    A1090 Highest Price in Supply Chain (25分)
    A1079 Total Sales of Supply Chain (25分)
  • 原文地址:https://www.cnblogs.com/liweibing/p/12794927.html
Copyright © 2011-2022 走看看