Java_ReentrantLock_重入锁之非公平锁NonfairSync源码分析
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自旋操作)