【无标题】

NonfairSync(非公平)

关键属性:
lock.lock() – 获取锁

直接尝试获取锁(通过CAS实现,调用unsafe方法将 state的值从0改成1)
成功就将持有锁的线程设置成当前线程,流程结束,失败则进入竞争锁的逻辑
竞争锁的逻辑
]
现在进入这个方法看看都做了些什么

tryAcquire(arg):这个方法是尝试获取锁,获取不到也不会阻塞,非公平实现直接调用了 nonfairTryAcquire(acquires) ,看下这个方法做了什么

可以看到,首先会获取state,如果state=0就再次尝试获取锁,然后还会判断是当前线程是不是占有锁的线程,如果这两样都失败了,那就没获取到锁,可以看到,在这个方法中并没有进行阻塞以及重试,只进行一次尝试。而且重入理论上没有限制,在溢出时才报错。
接下来是尝试获取锁失败的情况,这种情况下会将当前线程入队。

先来看看addWaiter(Node.EXCLUSIVE)做了些什么
其中 Node.EXCLUSIVE 是一个独占状态,改方法返回的是一个Node,即队列中的一个节点
首先,根据传入的mode封装了以及当前线程封装成一个Node。
第一次进来的时候队列的tail是null 所以会进入 enq方法将新节点入队
在这里先看一下tail不是null的情况
将新node的prev 设置成 tail
cas将tail设置为node
将旧tail的next设置为node
很简单的一个双向队列入队操作,这里不深入了
在这里需要注意的是,在这里已经生成了一个由Node构成的队列


现在来看下enq方法做了些什么

这里是一个for循环,详细讲解一下
首先取出tail,如果tail是null说明Node队列是空的
新建一个节点设置为head,并将tail也赋值为head
然后再次进入循环,这次的tail不是null,会进入else
将node的prev设置为tail
将tail设置为node,
将原来的tail的next设置为node
这是一个双向队列的入队操作

此时要注意的是 Node队列的第一个节点是 new Node()
到这里可以总结 addWaiter(Node.EXCLUSIVE),先创建了一个节点,然后将该节点加入一个队列,然后返回节点,如果没有获取到锁的线程有多个,那么在这里会存在多线程竞争的情况,所以对队列中节点的修改都是使用了CAS操作。

现在来看下acquireQueued(addWaiter(Node.EXCLUSIVE,),arg) 做了什么操作。

首先我们知道,第一个参数是一个在队列中的节点,第二个参数是我们需要的资源数
然后进入 acquireQueued(addWaiter(Node.EXCLUSIVE,),arg) 方法,这个方法也是多线程进行的

首先去获取当前节点的前驱节点,如果前驱节点是head 很明显只有第二个节点才符合这个条件。另外,这个地方也是存在多线程同时竞争的情况的。
如果当前节点是第二个节点 会进入第一个if
具体情况是 当前节点是第二个节点并且获取到锁了,就将当前线程设置为头节点,并将原来的头节点从队列中删除。
如果当前节点不是第二个节点,或者第二个节点没获取到锁
具体情况是 进入第二个if 首先进入 shouldParkAfterFailedAcquire

pred 是前驱节点,这里传的是head,node是 当前线程的节点
首先来分析 node是第二个节点,但是获取锁失败的情况 waitStatus 是 int 类型 默认值是 0
所以在这个情况下 ws 是 0 Node.SIGNAL = -1 所以会进入 else
这里会将前驱节点的waitStatus 通过cas设置为 Node.SIGNAL 其实这个值代表的是该节点之后的节点可以被唤醒
再来看node不是第二个节点 当前节点的前驱节点的waitStatus的默认值也为0所以流程和上面一致。
在这边可以想到的是,当一个线程获取了所,且有线程其余线程在队列中,那么队列中节点情况是:
头节点是 new Node() 且 waitStatus = Node.SIGNAL ,
其余节点是 new Node(Thread.currentThread,mode) 且 waitStatus = Node.SIGNAL,
尾节点是 new Node(Thread.currentThread,mode) 且 waitStatus = 0
执行结束之后会返回false,我们继续看外面的执行流程
外面后续会执行 parkAndCheckInterrupt()

可以看到,线程在这里对自己进行阻塞,到此为止,线程的获取锁的逻辑结束。接下来我们查看一下释放锁的过程。

lock.unlock() – 释放锁



首先摆出调用栈,可以看到lock.unlock() 调用 sync.release(1),我们分析release方法
这个方法的的详细信息是 java.util.concurrent.locks.AbstractQueuedSynchronizer#release
因为 ReentrantLock 中的 sync 继承了 AbstractQueuedSynchronizer

首先来看 tryRelease 方法 参数 releases 在这里的值是 1
1.获取state并将state的减少release(我们之前加锁的时候,将state用cas设置为了1,所以c的结果是0)
2.如果当前的线程不是持有锁的线程,抛出异常
3.如果c=0,把free的值设置为true,并设置当前占有所锁的线程是null
5.将status的值设置为c
6.返回free;
这里由于,当前线程是持有锁的,不存在竞争问题,所以不用使用cas方式对state进行赋值。
如果是重入的情况,那么会一直等到state减到0之后才会释放锁。

继续看后续流程,如果tryRelease返回 true,即state = 0 了 进入if语句

1.首先取出头节点(首先,如果只有一个线程的话,h肯定是null,因为队列是在产生了冲突,由没有获取到锁的线程生成的,在有冲突的情况下,实际上头节点就是当前线程,现在要释放锁了,理所当然需要让头节点出队,后续拿到锁的线程会成为新的头节点,这个后面再说)
2.如果头节点!=null且的waitStatus != 0 会运行 unparkSuccessor

头节点的waitStatus是 -1 将 头节点的waitStatus CAS 设置为0
然后获取下一个节点s 如果s = null 那么会进入第二个 if 如果 s != null 会进入第三个if
第二个if
如果 s=null,这种情况下 tail = node ,不会进入for循环,s = null
如果 s.waitStatus > 0 从 tail 到 node 进行查找,找到 node节点之后的第一个 waitStatus <= 0 的节点(之前全部设置的都是 -1)
第三个if 当 s != null 说明有需要被唤醒的节点,对节点对应的线程进行唤醒

释放锁的流程到这里就结束了,主要是将对头节点的 waitStatus 设置为0,并将ReentrantLock 的state设置成0,将当前占有锁的线程个设置为null。但是这里并没有将头节点出队。

现在来看被唤醒的线程会做些什么

线程被唤醒之后还在for循环里,再次进入for循环
线程能够获取到锁,将占有锁的线程设置为自己,将头节点设为这个线程对应的节点,将之前的头节点处对,然后返回,这里获取锁就完成了,并且最终返回false。
到这里新的线程也获取到了锁,后续的流程就和上面一致了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值