Java1.8 ReentrantLock加锁实现原理

提示:本文章纯属增强自己学习动力的,加深学习映像


前言

主要是讲一下最近学习的情况


 

目录

前言

一、ReentrantLock是什么?

二、上锁过程

1.实例化时可选锁类型

2.加锁流程图

3.非公平锁加锁流程源码分析

4.公平锁加锁流程源码分析

5.公平锁与非公平锁的区别

6.Synchronized与Reentrantlock的区别


一、ReentrantLock是什么?

ReentrantLock是Java中的一个可重入的互斥锁,它实现了Lock接口,并提供比使用Synchronized关键字更灵活的锁操作。ReentrantLock允许更细粒度的锁控制,比如公平锁和非公平锁的选择、尝试获取锁而不立即阻塞等。

下面我结合最近看的Java源码实现整理的上锁过程。

二、上锁过程

1.实例化时可选锁类型

代码如下(示例):

从上面源码可以看在创建ReentrantLock时,我们可以调用 带参构造函数创建出一个公平锁(FairSync)或者非公平锁(NonfairSync)。默认的是非公平锁。

2.加锁流程图

3.非公平锁加锁流程源码分析

  • 如上图非公平锁源码中的lock()方法实现,我们可以看到非公平锁在调用lock()方法时,首先或去尝试CAS修改state状态(0—1)获取锁。如果失败就去调用AQS中的acquire(1)方法加入等待队列中。

  • 如上图在acquire方法中源码实现,先调用tryAcquire方法尝试获取锁,这个方法会调用NonfairSync中实现的tryAcquire方法如下图源码所示:

  • 在nonfairTryAcquire方法中,首先获取当前锁状态state,判断锁是不是已经被占用,如果锁没有被占用使用CAS获取锁,获取成功返回true获取失败返回false。如果被占用判断当前占用锁的线程是不是现在需要获取锁的线程,如果是锁状态state值加1返回true,如果不是返回false。

  •  如果tryAcquire没有获取到锁,需要调用addWaiter(Node.EXCLUSIVE)方法构造排队节点Node对象。根据下面源码分析,如果排队队列不为空就将当前新建节点放在队列尾,如果队列为空调用enq()方法。

  •  enq方法如下图所示,如果队列为空进入enq方法后,会先创建一个虚拟节点作为头节点head,然后再将当前线程节点放入队尾。返回当前线程的node节点。

  • 将当前线程节点加入等待队列后,需要调用acquireQueued方法,将当前线程以及尝试获取的资源量(arg)封装在一个节点(Node)中,并将此节点加入到等待队列(FIFO 队列)中。同时,该方法会尝试让线程在队列中等待,直到能够获取到同步状态为止。
  • 进入acquireQueued后会进入一个循环,在这个循环中,线程会自旋尝试获取同步状态。如果同步状态能够被成功获取(即满足获取条件),则方法返回 true,线程成功获取到同步状态。
  • 线程会调用shouldParkAfterFailedAcquire方法检查其前驱节点(即队列中排在它前面的节点)的状态。如果前驱节点已经是取消状态(CANCELLED),则当前线程会尝试将队列中所有已取消的节点清理掉,并尝试将自己设置为下一个需要检查的节点。
  • 如果线程无法立即获取到同步状态,并且前驱节点还未到达释放同步状态的条件,那么线程会调用本地方法park()进入等待状态,直到被前驱节点唤醒或响应其他中断/超时条件。

4.公平锁加锁流程源码分析

  • 公平锁上锁调用lock()方法后,直接调用AQS中的acquire(1)方法加入等待队列中。

  •  如上图在acquire方法中源码实现,先调用tryAcquire方法尝试获取锁,这个方法会调用FairSync中实现的tryAcquire方法如下图源码所示:

  •  tryAcquire方法中先获取当前锁的状态,如果锁未被占用,调用hasQueuedPredecessors方法从head向后查看等待队列中有没有等待执行的线程节点。如果没有使用CAS获取锁,成功返会true。如果有判断当前线程与占用锁线程是否一致,如果一致锁state+1,获取锁成功否则返回false。
  •  如果tryAcquire没有获取到锁,需要调用addWaiter(Node.EXCLUSIVE)方法构造排队节点Node对象。根据下面源码分析,如果排队队列不为空就将当前新建节点放在队列尾,如果队列为空调用enq()方法。

  •  enq方法如下图所示,如果队列为空进入enq方法后,会先创建一个虚拟节点作为头节点head,然后再将当前线程节点放入队尾。返回当前线程的node节点。

  • 将当前线程节点加入等待队列后,需要调用acquireQueued方法,将当前线程以及尝试获取的资源量(arg)封装在一个节点(Node)中,并将此节点加入到等待队列(FIFO 队列)中。同时,该方法会尝试让线程在队列中等待,直到能够获取到同步状态为止。
  • 进入acquireQueued后会进入一个循环,在这个循环中,线程会自旋尝试获取同步状态。如果同步状态能够被成功获取(即满足获取条件),则方法返回 true,线程成功获取到同步状态。
  • 线程会调用shouldParkAfterFailedAcquire方法检查其前驱节点(即队列中排在它前面的节点)的状态。如果前驱节点已经是取消状态(CANCELLED),则当前线程会尝试将队列中所有已取消的节点清理掉,并尝试将自己设置为下一个需要检查的节点。
  • 如果线程无法立即获取到同步状态,并且前驱节点还未到达释放同步状态的条件,那么线程会调用本地方法park()进入等待状态,直到被前驱节点唤醒或响应其他中断/超时条件。

5.公平锁与非公平锁的区别

  1. 加锁阶段,非公平锁会直接CAS先尝试获取锁,公平锁需要排队按照先进先出原则有序执行。
  2. 当tryAcquire中检查到锁释放时,非公平锁新来的线程不需要检查队列情况先尝试获取锁,公平锁需要检查队列中是否有排队线程。

6.Synchronized与Reentrantlock的区别

  1. 来源不同,Synchronized是Java关键字,ReentrantLock是JUC并发包中的具体实现类。
  2. 操作不同,Synchronized会自动加锁与释放锁,ReentrantLock需要程序员手动加锁与解锁。
  3. 实现不同,Synchronized是JVM实现的,ReentrantLock是通过AQS+CAS实现的。
  4. 锁类型不同,Synchronized是非公平锁,ReentrantLock可以通过构造函数选择公平锁或非公平锁。
  5. 锁资源不同,Synchronized是对象锁,锁信息保存在对象头的MarkWord中,ReentrantLock是通过代码中volatile关键字修饰的int state状态来标识锁状态。
  6. 锁升级,Synchronized底层有根据线程竞争层度的锁升级过程,无锁——偏向锁——轻量级锁——重量级锁,ReentrantLock没有锁升级过程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值