ReentrantLock 是常用的锁,相对于Synchronized ,lock锁更人性化,阅读性更强

从LOCK切入

考虑下面的场景如果有A,B线程,同时去执行lock.lock(Lock lock = new ReentrantLock 为全局属性),当A线程抢到锁以后,此时B线程做了哪些事情

要知道B线程做了那些事情,就要知道一个类AbstractQueueSynchronizer(简称AQS),它负责将锁竞争失败线程存储起来

  Lock lock =  new ReentrantLock();  
try{
lock.lock();
System.out.println("测试");
}finally {
lock.unlock();
}

  

AQS 分析

     要理解锁的运行原理,至少要了解下面的几个问题

      ①,如何锁定(或者说锁定的标准是什么)

      ②,竞争锁失败,怎么处理

       ③,如何释放锁

      ④,如何唤醒其他线程重新竞争

 针对上面的4个问题,分析一下ReenTrantLock

如何锁定

 //当执行lock.lock()竞争锁
final void lock() {
//1,A,B线程竞争ReenTrantLock->AQS属性->state属性
//2,乐观锁的形式更新state,如A线程先将state更新为1,就代表A线程竞争锁成功

if (compareAndSetState(0, 1))
//设置锁持有线程
setExclusiveOwnerThread(Thread.currentThread());
else
//B线程竞争锁失败,执行下面方法
acquire(1);
}

要理解AQS的原理中,理解state的状态值变化很重要

  state=0 ,node节点创建时state=0

state=-1,后继线程可以考虑阻塞啦,因为后继线程不是下一个需要执行竞争锁的(在添加一个新的节点后,那么的它的pre节点的state会变成-1)

state = 1,节点失效,如:当tryLock(1000,TimeUnit.MILLISECOND) 超时时,节点状态值

B线程竞争锁失败,处理方式

  1,尝试重新获取锁(目的)

   1.1,看看前面锁是否释放(先下手为强,假如成功尼,哈哈,满满的求生欲)
                  1.2,查看是否同一个线程竞争锁,考虑重入锁

  2,仍然失败,将B线程封装一个新的node节点,插入一个双向队列的尾部

  3,开始自旋(死循环),直到获取锁为止

    3.1,判断当前线程是否有资格竞争锁

    3.2,无资格或者竞争失败的情况下,考虑是否暂停B线程线程(A线程释放锁的时候,会唤醒B线程)

从源码角度,来解析一下上面的内容

public final void acquire(int arg) {
//尝试获取锁,失败,考虑可重入锁
if (!tryAcquire(arg) &&
//addWaiter 添加到双向队列的尾结点
//acquireQueued,开始自旋(死循环)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果当前线程在竞争过程中存在中断情况,设置当前线程中断
selfInterrupt();
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
} final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
} final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//判断当前这个节点前节点是否是head节点,是的话,重新进行竞争操作(此时A线程可能还木有释放锁)
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果失败或者前节点不是head节点,根据实际情况,考虑一下暂停当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

背景:A线程竞争锁成功,B线程竞争锁失败,AQS队列为空,此时B线程准备加入队列

1,首先添加一个空节点,head,tail 链接到此节点

2,B线程节点,加入AQS队列,A线程释放锁的时候,直接唤醒B线程竞争锁,竞争成功,队列前进一位即可

A线程释放锁

1,做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁

2,在双向队列中,查找下一个节点,唤醒这个节点的线程

如果A线程直接抢到锁啦说明A线程所在的节点并木有添加到双向队列中,那么下一个节点就是双向队列的头节点

如果A线程所在的节点加入了双向队列,那么head节点为A线程所在的节点,下一个节点为head节点的下一个节点

 public void unlock() {
sync.release(1);
}

//做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
} public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//在双向队列中,查找下一个节点,唤醒这个节点的线程

Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

聊聊ReentrantLock实现原理的更多相关文章

  1. ReentrantLock实现原理深入探究

    前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...

  2. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

  3. 【Java并发编程】15、ReentrantLock实现原理深入探究

    原文已经写得非常详细了,直接把大神的文章转发过来了  https://www.cnblogs.com/xrq730/p/4979021.html 前言 这篇文章被归到Java基础分类中,其实真的一点都 ...

  4. ReentrantLock实现原理

    以下是本篇文章的大纲 1 synchronized和lock 1.1 synchronized的局限性 1.2 Lock简介 2 AQS 3 lock()与unlock()实现原理 3.1 基础知识 ...

  5. ReentrantLock实现原理及源码分析

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...

  6. ReentrantLock 实现原理

    使用 synchronize 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现. 而 ReentrantLock 就是一个普通的类,它是基于 AQS(Abstr ...

  7. 解析ReentrantLock实现原理

    在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock(Lock的实现主要有ReentrantLock.ReadLock和WriteLock).synchronize ...

  8. 聊聊ReentrantLock的内部实现

    大家都用过ReentrantLock,但是大家对内部实现是否足够了解呢,下面我就简单说一下其中的实现原理. ReentrantLock是可重入锁,也就是同一个线程可以多次获取锁,每获取一次就会进行一次 ...

  9. ReentrantLock的原理解析

    重入锁(ReentrantLock)是一种可重入无阻塞的同步机制.性能同synchronized接近(老版本jdk中性能很差). 下面重点看下常用的lock()和unlock()方法的实现原理. lo ...

随机推荐

  1. 项目实战:流水线图像显示控件(列刷新、1ms一次、缩放、拽拖、拽拖预览、性能优化、支持OpenGL GPU加速)

      需求   流水线图像扫描采集控件(带模拟数据测试)性能需求  1.需至少满足可1ms接收一次列数据,而不丢包(接收后可不必立马显示)  2.图片刷新率可达30HZ:限制需求  1.图片高度最小只能 ...

  2. python接口自动化测试遇到的问题及解决方案

    工作中xml中的某一个字段是全网唯一,这就需要进行参数化处理.此次对这一个字段进行参数化处理引用了random模块和index()函数.代码如下: #!/usr/bin/python # -*- co ...

  3. kubernetes 基础知识

    1. kubernetes 包含几个组件 Kubernetes是什么:针对容器编排的一种分布式架构,是自动化容器操作的开源平台. 服务发现.内建负载均衡.强大的故障发现和自我修复机制.服务滚动升级和在 ...

  4. 5G应用的实时决策

    背景概述 尽管近几年很多供应商在不断重申着他们对VoltDB持续输出的专业认可,VoltDB也随着技术发展在不断增加一些流行技术词汇,但是真正让大家了解某个技术产品持续演进的特性,单单依靠增加几个技术 ...

  5. think PHP5.1使用时 session重定向丢失问题

    查了很多资料,也看了redirect底层代码,具体来说,还是多个用的地方不太对.做个笔记防忘记: 遇重定向后丢失session时: 1.php.ini配置文件,不要自动启动,默认是0,session. ...

  6. Antisymmetry

    题意描述 Antisymmetry 求给定的字符串的子串集合中为"反对串"的个数. 反对串的定义为,将这个字符串 \(0\) 和 \(1\) 取反后,再将整个串反过来和原串一样,就 ...

  7. 小白如何学习PyTorch】25 Keras的API详解(下)缓存激活,内存输出,并发解决

    [新闻]:机器学习炼丹术的粉丝的人工智能交流群已经建立,目前有目标检测.医学图像.时间序列等多个目标为技术学习的分群和水群唠嗑答疑解惑的总群,欢迎大家加炼丹兄为好友,加入炼丹协会.微信:cyx6450 ...

  8. 完全卸载node.js

    1.通过控制面板卸载node.js 2.删除安装所在文件夹下的nodejs文件夹[我的是 C:\Program Files\nodejs] 3.删除C:\Users\xxx(自己电脑的名字)下的.np ...

  9. egit版本对应关系。

    egit版本对应关系. http://wiki.eclipse.org/EGit/FAQ#What_versions_of_Eclipse_does_EGit_target.3F

  10. JavaScript 读取CSS3 transform

    某些场景需要读取 css3 transform的属性 例如 transform:translate(10px,10px) rotate(-45deg); 这该怎么读取呢,正则表达式?毫无疑问这很坑爹 ...