Java_ReentrantLock_重入锁之非公平锁NonfairSync源码分析

ReentrantLock-基本概念

ReentrantLock:重入锁,表示支持重新进入的锁。即,当先线程调用ReentrantLock的lock方法获取锁之后,再次调用lock方法,是不会阻塞获取锁的,直接增加重入次数就可以了。

同理,synchronized也是可重入锁的一种。重入锁设计的目的是为了避免线程的死锁。

synchronized重入锁的实现是基于JVM层面实现的

ReentrantLock是API层面也就是JDK层面实现的,需要lock()和unlock()方法配合try{}finally{}使用

ReentrantLock-高级功能

中断等待锁的线程的机制:通过 lock.lockInterruptibly() 方法来实现,即优先响应中断。可以中止等待,去做其它事情。

可以指定是公平锁还是非公平锁:synchronized只能提供非公平锁。

ReentrantLock类中lock方法是抽象的,具体由两个方法来实现。即  NonfairSync 和 FairSync (非公平锁和公平锁)

1.以非公平锁为例,来看看 lock 中的实现

1. 非公平锁和公平锁最大的区别在于,在非公平锁中我抢占锁的逻辑是,不管有没有线程排队,我先上来 cas 去抢占一下
2. CAS 成功,就表示成功获得了锁
3. CAS 失败,调用 acquire(1)走锁竞争逻辑

1.1让我们先来了解一下CAS的原理

首先我们需要明白一点:锁拥有一个共享的数据来记录其基本状态(无锁/有锁  状态)---state

这段代码我们可以这样理解:通过cas乐观锁的方式去竞争锁,如果当前内存中的state的值和预期值expect相等,则更新为update.更新成功返回true,表示竞争锁成功。

这个操作是原子的,不会出现线程安全问题,这里面涉及到Unsafe这个类的操作,以及涉及到 state 这个属性的意义。

1.2state属性的意义

state 是 AQS 中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示

1. 当 state=0 时,表示无锁状态

2. 当 state>0 时,表示已经有线程获得了锁,也就是 state=1,但是因为ReentrantLock 允许重入,所以同一个线程多次获得同步锁的时候,state 会递增,比如重入 5 次,那么 state=5。 而在释放锁的时候,同样需要释放 5 次直到 state=0其他线程才有资格获得锁

1.3关于Unsafe类

Unsafe 类是在 sun.misc 包下,不属于 Java 标准。但是很多 Java 的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe 类开发的,比如 Netty、Hadoop、Kafka 等;

Unsafe 可认为是 Java 中留下的后门,提供了一些低层次操作,如直接内存访问、线程的挂起和恢复、CAS、线程同步、内存屏障而 CAS 就是 Unsafe 类中提供的一个原子操作。

第一个参数为需要改变的对象

第二个为偏移量(即之前求出来的 headOffset 的值)

第三个参数为期待的值

第四个为更新后的值整个方法的作用是如果当前时刻的值等于预期值 var4 相等,则更新为新的期望值 var5,如果更新成功,则返回 true,否则返回 false;

1.3.1  stateOffset

一个 Java 对象可以看成是一段内存,每个字段都得按照一定的顺序放在这段内存里,通过这个方法可以准确地告诉你某个字段相对于对象的起始内存地址的字节偏移。用于在后面的 compareAndSwapInt 中,去根据偏移量找到对象在内存中的具体位置.

所以 stateOffset 表示 state 这个字段在 AQS 类的内存中相对于该类首地址的偏移量

2.AQS---acquire(1)

acquire 是 AQS 中的方法,如果 CAS 操作未能成功,说明 state 已经不为 0,此时继续 acquire(1)操作

1. 通过 tryAcquire 尝试获取独占锁,如果成功返回 true,失败返回 false

2. 如果 tryAcquire 失败,则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 队列尾部

3. acquireQueued,将 Node 作为参数,通过自旋去尝试获取锁。

2.1关于tryAcquire(arg)

           重写了AQS类中的tryAcquire方法--尝试去获取锁,如果成功返回true,不成功返回false

2.1.1ReentrantLock  --  nonfairTryAcquire(acquires)

1. 获取当前线程,判断当前的锁的状态

2. 如果 state=0 表示当前是无锁状态,通过 cas 更新 state 状态的值

3. 当前线程是属于重入,则增加重入次数

2.2AQS  ---   addWaiter

当 tryAcquire 方法获取锁失败以后,则会先调用 addWaiter 将当前线程封装成Node.入参 mode 表示当前节点的状态,传递的参数是 Node.EXCLUSIVE,表示独占状态。意味着重入锁用到了 AQS 的独占锁功能

1. 将当前线程封装成 Node

2. 当前链表中的 tail 节点是否为空,如果不为空,则通过 cas 操作把当前线程的node 添加到 AQS 队列

3. 如果为空或者 cas 失败,调用 enq 将节点添加到 AQS 队列(enp自旋操作)

enq(node)方法,通过自旋操作把当前节点加入到队列当中。

2.3AQS -- acquireQueued

通过addWaiter方法把线程添加到链表中后,会接着把node作为参数传递给acquireQueued方法,去竞争锁。具体操作步骤变化有以下几点。

1.获取当前节点的prev节点

2.判断prev节点是否为head节点,如果是,就有资格去调用tryAcquire()方法抢占锁

3.抢占锁成功以后,把获得锁的节点设置为head,并且移除原来初始化的head节点。

4.若是抢占失败,则根据waitStatus决定是否来挂起线程

5.最后通过cancelAcquire取消获得锁的操作

时序图1

附:时序图2

附:图解分析

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值