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
    评论
(ReentrantLock)是一种独占,也就是说同一时间只能有一个线程持有该。与 synchronized 关键字不同的是,可以支持公平公平两种模式,而 synchronized 关键字只支持公平的实现原理是基于 AQS(AbstractQueuedSynchronizer)框架,利用了 CAS(Compare And Swap)操作和 volatile 关键字。 的核心思想是“可性”,也就是说如果当前线程已经持有了该,那么它可以复地获取该而不会被阻塞。在内部,使用了一个计数器来记录当前线程持有该的次数。每当该线程获取一次时,计数器就加 1,释放一次时,计数器就减 1,只有当计数器为 0 时,其他线程才有机会获取该的基本使用方法如下: ```java import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest { private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " get lock"); Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " release lock"); } }, "Thread-1").start(); new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " get lock"); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " release lock"); } }, "Thread-2").start(); } } ``` 在上面的示例代码中,我们创建了两个线程,分别尝试获取。由于支持可性,因此第二个线程可以成功地获取到该,而不会被阻塞。当第一个线程释放后,第二个线程才会获取到并执行相应的操作。 需要注意的是,使用时一定要记得在 finally 块中释放,否则可能会导致死的问题。同时,在获取时也可以设置超时时间,避免由于获取失败而导致的线程阻塞问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值