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)。

    人总得做点什么 ,不是么
  • 相关阅读:
    (void) (&_x == &_y)的作用
    GNU C 与 ANSI C(下)
    GNU C 与 ANSI C(上)
    “多个单核CPU”与“单个多核CPU”哪种方式性能较强?
    ARM 处理器寻址方式之间接寻址的几种表达
    Video for Linux Two API Specification
    UVC 驱动调用过程与驱动框架的简单分析
    线程安全
    合法的立即数的判断
    Redis的Java客户端Jedis
  • 原文地址:https://www.cnblogs.com/liweibing/p/12794927.html
Copyright © 2011-2022 走看看