JDK中lock锁的机制,其底层是一种无锁的架构实现的,公平锁和非公平锁

简述JDK中lock锁的机制,其底层是一种无锁的架构实现的,是否知道其是如何实现的

synchronized与lock

lock是一个接口,而synchronized是在JVM层面实现的。synchronized释放锁有两种方式:

  1. 获取锁的线程执行完同步代码,释放锁 。
  2. 线程执行发生异常,jvm会让线程释放锁。

lock锁的释放,出现异常时必须在finally中释放锁,不然容易造成线程死锁。lock显式获取锁和释放锁,提供超时获取锁、可中断地获取锁。

synchronized是以隐式地获取和释放锁,synchronized无法中断一个正在等待获取锁的线程。

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作。

具体的悲观锁和乐观锁的详细介绍请参考这篇文章[浅谈数据库乐观锁、悲观锁]

JDK5中增加了一个Lock接口实现类ReentrantLock.它不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票,定时锁,等候和中断锁等.它们的性能在不同的情况下会有不同。

在资源竞争不是很激烈的情况下,synchronized的性能要由于ReentrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降得非常快,而ReentrantLock的性能基本保持不变.

接下来我们会进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState

lock源码

在阅读源码的成长的过程中,有很多人会遇到很多困难,一个是源码太多,另一方面是源码看不懂。在阅读源码方面,我提供一些个人的建议:

  1. 第一个是抓主舍次,看源码的时候,很多人会发现源码太长太多,看不下去,这就要求我们抓住哪些是核心的方法,哪些是次要的方法。当舍去次要方法,就会发现代码精简和很多,会大大提高我们阅读源码的信心。
  2. 第二个是不要死扣,有人看源码会一行一行的死扣,当看到某一行看不懂,就一直停在那里死扣,知道看懂为止,其实很多时候,虽然看不懂代码,但是可以从变量名和方法名知道该代码的作用,java中都是见名知意的。

接下来进入阅读lock的源码部分,在lock的接口中,主要的方法如下:

public interface Lock {// 加锁void lock()// 尝试获取锁boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 解锁void unlock();
}

在lock接口的实现类中,最主要的就是ReentrantLock,来看看ReentrantLocklock()方法的源码:

    // 默认构造方法,非公平锁public ReentrantLock() {sync = new NonfairSync();}// 构造方法,公平锁public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}// 加锁public void lock() {sync.lock();}

在初始化lock实例对象的时候,可以提供一个boolean的参数,也可以不提供该参数。提供该参数就是公平锁,不提供该参数就是非公平锁。

什么是非公平锁和公平锁呢?

非公平锁就是不按照线程先来后到的时间顺序进行竞争锁,后到的线程也能够获取到锁,公平锁就是按照线程先来后到的顺序进行获取锁,后到的线程只能等前面的线程都获取锁完毕才执行获取锁的操作,执行有序。

我们来看看lock()这个方法,这个有区分公平锁和非公平锁,这个两者的实现不同,先来看看公平锁,源码如下:

// 直接调用 acquire(1)
final void lock() {acquire(1);}

我们来看看acquire(1)的源码如下:

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

这里的判断条件主要做两件事:

  1. 通关过该方法tryAcquire(arg)尝试的获取锁
  2. 若是没有获取到锁,通过该方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg)就将当前的线程加入到存储等待线程的队列中。

其中tryAcquire(arg)是尝试获取锁,这个方法是公平锁的核心之一,它的源码如下:

protected final boolean tryAcquire(int acquires) {// 获取当前线程 final Thread current = Thread.currentThread();// 获取当前线程拥有着的状态int c = getState();// 若为0,说明当前线程拥有着已经释放锁if (c == 0) {// 判断线程队列中是否有,排在前面的线程等待着锁,若是没有设置线程的状态为1。if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {// 设置线程的拥有着为当前线程setExclusiveOwnerThread(current);return true;}// 若是当前的线程的锁的拥有者就是当前线程,可重入锁} else if (current == getExclusiveOwnerThread()) {// 执行状态值+1int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");// 设置status的值为nextcsetState(nextc);return true;}return false;}

tryAcquire()方法中,主要是做了以下几件事:

  1. 判断当前线程的锁的拥有者的状态值是否为0,若为0,通过该方法hasQueuedPredecessors()再判断等待线程队列中,是否存在排在前面的线程。
  2. 若是没有通过该方法 compareAndSetState(0, acquires)设置当前的线程状态为1。
  3. 将线程拥有者设为当前线程setExclusiveOwnerThread(current)
  4. 若是当前线程的锁的拥有者的状态值不为0,说明当前的锁已经被占用,通过current == getExclusiveOwnerThread()判断锁的拥有者的线程,是否为当前线程,实现锁的可重入。
  5. 若是当前线程将线程的状态值+1,并更新状态值。

公平锁的tryAcquire(),实现的原理图如下:

在这里插入图片描述

我们来看看acquireQueued()方法,该方法是将线程加入等待的线程队列中,源码如下:

final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;// 死循环处理for (;;) {// 获取前置线程节点final Node p = node.predecessor();// 这里又尝试的去获取锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;// 直接return  interruptedreturn interrupted;}// 在获取锁失败后,应该将线程Park(暂停)if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

acquireQueued()方法主要执行以下几件事:

  1. 死循环处理等待线程中的前置节点,并尝试获取锁,若是p == head && tryAcquire(arg),则跳出循环,即获取锁成功。
  2. 若是获取锁不成功shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()就会将线程暂停。

acquire(int arg)方法中,最后若是条件成立,执行下面的源码:

selfInterrupt();
// 实际执行的代码为
Thread.currentThread().interrupt();

即尝试获取锁失败,就会将锁加入等待的线程队列中,并让线程处于中断等待。公平锁lock()方法执行的原理图如下:

在这里插入图片描述

之所以画这些原理的的原因,是为后面写一个自己的锁做铺垫,因为你要实现和前人差不多的东西,你必须了解该东西执行的步骤,最后得出的结果,执行的过程是怎么样的。

有了流程图,在后面的实现自己的东西才能一步一步的进行。这也是阅读源码的必要之一。

lock()方法,其实在lock()方法中,已经包含了两方面:

  1. 锁方法lock()
  2. 尝试获取锁方法tryAquire()

接下来,我们来看一下unlock()方法的源码。

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

直接调用release(1)方法,来看release方法源码如下:

    public final boolean release(int arg) {// 尝试释放当前节点if (tryRelease(arg)) {// 取出头节点Node h = head;if (h != null && h.waitStatus != 0)// 释放锁后要即使唤醒等待的线程来获取锁unparkSuccessor(h);return true;}return false;}

通过调用tryRelease(arg),尝试释放当前节点,若是释放锁成功,就会获取的等待队列中的头节点,就会即使唤醒等待队列中的等待线程来获取锁。接下来看看tryRelease(arg)的源码如下:

// 尝试释放锁protected final boolean tryRelease(int releases) {// 将当前状态值-1int c = getState() - releases;// 判断当前线程是否是锁的拥有者,若不是直接抛出异常,非法操作,直接一点的解释就是,你都没有拥有锁,还来释放锁,这不是骗人的嘛if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//执行释放锁操作 1.若状态值=0   2.将当前的锁的拥有者设为nullif (c == 0) {free = true;setExclusiveOwnerThread(null);}// 重新更新status的状态值setState(c);return free;}

总结上面的几个方法,unlock释放锁方法的执行原理图如下:

在这里插入图片描述

对于非公平锁与公平锁的区别,在非公平锁尝试获取锁中不会执行hasQueuedPredecessors()去判断是否队列中还有等待的前置节点线程。

如下面的非公平锁,尝试获取锁nonfairTryAcquire()源码如下:

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 直接就将status-1,并不会判断是否还有前置线程在等待if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

以上就是公平锁和非公平锁的主要的核心方法的源码,接下来我们实现自己的一个锁,首先依据前面的分析中,要实现自己的锁,拥有的锁的核心属性如下:

  1. 状态值status,0为未占用锁,1未占用锁,并且是线程安全的。
  2. 等待线程队列,用于存放获取锁的等待线程。
  3. 当前线程的拥有者。

lock锁的核心的Api如下:

  1. lock方法
  2. trylock方法
  3. unlock方法

依据以上的核心思想来实现自己的锁,首先定义状态值status,使用的是AtomicInteger原子变量来存放状态值,实现该状态值的并发安全和可见性。定义如下:

// 线程的状态 0表示当前没有线程占用   1表示有线程占用AtomicInteger status =new AtomicInteger();

接下来定义等待线程队列,使用LinkedBlockingQueue队列来装线程,定义如下:

// 等待的线程
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<Thread>();

最后的属性为当前锁的拥有者,直接就用Thread来封装,定义如下:

// 当前线程拥有者
Thread ownerThread =null;

接下来定义lock()方法,依据上面的源码分析,在lock方法中主要执行的几件事如下:

  1. 死循环的处理等待线程队列中的线程,知道获取锁成功,将该线程从队列中删除,跳出循环。
  2. 获取锁不成功,线程处于暂停等待。
    @Overridepublic void lock() {// TODO Auto-generated method stub// 尝试获取锁if (!tryLock()) {// 获取锁失败,将锁加入等待的队列中waitersQueue.add(Thread.currentThread());// 死循环处理队列中的锁,不断的获取锁for (;;) {if (tryLock()) {// 直到获取锁成功,将该线程从等待队列中删除waitersQueue.poll();// 直接返回return;} else {// 获取锁不成功,就直接暂停等待。LockSupport.park();}}}}

然后是trylock方法,依据上面的源码分析,在trylock中主要执行的以下几件事:

  1. 判断当前拥有锁的线程的状态是否为0,为0,执行状态值+1,并将当前线程设置为锁拥有者。
  2. 实现锁可重入
    @Overridepublic boolean tryLock() {// 判断是否有现成占用if (status.get()==0) {// 执行状态值加1if (status.compareAndSet(0, 1)) {// 将当前线程设置为锁拥有者ownerThread = Thread.currentThread();return true;} else if(ownerThread==Thread.currentThread())  {// 实现锁可重入status.set(status.get()+1);}}return false;}

最后就是unlock方法,依据上面的源码分析,在unlock中主要执行的事情如下:

  1. 判断当前线程是否是锁拥有者,若不是直接抛出异常。
  2. 判断状态值是否为0,并将锁拥有者清空,唤醒等待的线程。
    @Overridepublic void unlock() {// TODO Auto-generated method stub// 判断当前线程是否是锁拥有者if (ownerThread!=Thread.currentThread()) {throw new RuntimeException("非法操作");}// 判断状态值是否为0if (status.decrementAndGet()==0) {// 清空锁拥有着ownerThread = null;// 从等待队列中获取前置线程Thread t = waitersQueue.peek();if (t!=null) {// 并立即唤醒该线程LockSupport.unpark(t);}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/192569.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

神经网络 代价函数

神经网络 代价函数 首先引入一些便于稍后讨论的新标记方法&#xff1a; 假设神经网络的训练样本有 m m m个&#xff0c;每个包含一组输入 x x x和一组输出信号 y y y&#xff0c; L L L表示神经网络层数&#xff0c; S I S_I SI​表示每层的neuron个数( S l S_l Sl​表示输出…

使用idea如何快速的搭建ssm的开发环境

文章目录 唠嗑部分言归正传1、打开idea&#xff0c;点击新建项目2、填写信息3、找到pom.xml先添加springboot父依赖4、添加其他依赖5、编写启动类、配置文件6、连接创建数据库、创建案例表7、安装MybatisX插件8、逆向工程9、编写controller10、启动项目、测试 结语 唠嗑部分 小…

MIT线性代数笔记-第21讲-特征值,特征向量

目录 21.特征值&#xff0c;特征向量打赏 21.特征值&#xff0c;特征向量 对于一个方阵 A A A&#xff0c;若 A x ⃗ λ x ⃗ A \vec{x} \lambda \vec{x} Ax λx &#xff0c;即 A x ⃗ A \vec{x} Ax 平行于 x ⃗ \vec{x} x &#xff0c;那么 λ \lambda λ是 A A A的特征值…

怎么防止死锁

目录 一、死锁的概念 1、互斥条件 2、 占有和等待条件 3、非抢占条件&#xff08;No Preemption&#xff09;&#xff1a; 4、环路等待条件&#xff08;Circular Wait&#xff09;&#xff1a; 二、防止死锁 死锁预防&#xff08;Deadlock Prevention&#xff09;&#xf…

内网穿透工具获取一个公网ip

下载地址&#xff1a;点击即可下载很简单 然后将他复制到上面的命令行窗口直接回车

编程怎么学才能快速入门,分享一款中文编程工具快速学习编程思路,中文编程工具之边条主控菜单构件简介

编程怎么学才能快速入门&#xff0c;分享一款中文编程工具快速学习编程思路&#xff0c;中文编程工具之边条主控菜单构件简介 一、前言 零基础自学编程&#xff0c;中文编程工具下载&#xff0c;中文编程工具构件之扩展系统菜单构件教程编程系统化教程链接https://jywxz.blog…

SALib敏感性分析入门实践笔记

1. 敏感性分析 敏感性分析是指从定量分析的角度研究有关因素发生某种变化对某一个或一组关键指标影响程度的一种不确定分析技术。 其实质是通过逐一改变相关变量数值的方法来解释关键指标受这些因素变动影响大小的规律。 敏感性因素一般可选择主要参数&#xff08;如销售收入、…

autojs-ui悬浮按钮模板

注释很详细&#xff0c;直接上代码 涵盖很多常用知识点&#xff0c;也可当知识点看 运行效果长这样&#xff1a; 开始按钮相当于开关&#xff0c;按钮内容会随点击变换控制台按钮可让运行框显示或隐藏退出按钮退出程序并在3s后关闭运行框只需在对应函数内添加需要实现的内容即可…

算法复习,数据结构 ,算法特性,冒泡法动态演示,复杂度,辗转相除法*,寻找最大公因数

算法复习 知识点 1. 程序 数据结构 算法 2. 算法&#xff1a; 求解问题的策略数据结构&#xff1a;问题的数学模型程序&#xff1a;微计算机处理问题编制的一组指令 3. **特性 ** 有穷性&#xff1a;算法在执行有穷步后能结束确定性&#xff1a;每一指令有确切的含义&a…

2023年12月4日支付宝蚂蚁庄园小课堂小鸡宝宝考考你今日正确答案是什么?

问题&#xff1a;你知道电杆上安装的“小风车”有什么用途吗&#xff1f; 答案&#xff1a;防止鸟类筑巢 解析&#xff1a;小风车一般做成橙色&#xff0c;因为橙色是一种可令野鸟产生恐慌感的颜色&#xff1b;小风车在转动时&#xff0c;会发出令野鸟害怕的噪声&#xff1b;…

LLM推理部署(四):一个用于训练、部署和评估基于大型语言模型的聊天机器人的开放平台FastChat

FastChat是用于对话机器人模型训练、部署、评估的开放平台。体验地址为&#xff1a;https://chat.lmsys.org/&#xff0c;该体验平台主要是为了收集人类的真实反馈&#xff0c;目前已经支持30多种大模型&#xff0c;已经收到500万的请求&#xff0c;收集了10万调人类对比大模型…

4个解决特定的任务的Pandas高效代码

在本文中&#xff0c;我将分享4个在一行代码中完成的Pandas操作。这些操作可以有效地解决特定的任务&#xff0c;并以一种好的方式给出结果。 从列表中创建字典 我有一份商品清单&#xff0c;我想看看它们的分布情况。更具体地说&#xff1a;希望得到唯一值以及它们在列表中出…

uniapp 微信小程序连接蓝牙卡死

解决方法&#xff0c;需要同意隐私保护协议&#xff0c;否则不能开启蓝牙权限和定位权限&#xff0c;会导致定位失败

实用工具网站合集值得收藏![搜嗖工具箱]

最近一段时间有点忙&#xff0c;一直没有更新在此给大家说声抱歉哈&#xff0c;有些小伙伴儿私信说想要用到的工具&#xff0c;茶壶儿也会尽可能满足大家&#xff01;今天我们要分享的工具主要有以下几款&#xff0c;我们来一起看一下吧&#xff1f; 一帧秒创 https://aigc.y…

万界星空科技灯具行业MES介绍

中国是LED照明产品最大的生产制造国&#xff0c;如今&#xff0c;我国初步形成了包括LED外延片的生产、LED芯片的制备、LED芯片的封装以及LED产品应用在内的较为完超为产业链&#xff0c;随着LED照明市场渗诱率的快速警升&#xff0c;LED下游应用市场将会越来越广阔。这也将推动…

电机工厂怎么才能有效的管理生产?

电机工厂要想更有效地管理&#xff0c;可以从以下几个方面入手&#xff1a;采用数字化软件管理工具 数字化软件管理工具可以帮助电机工厂实现工艺流程自动化、数据管理、生产计划排程等。例如&#xff0c;采用ERP&#xff08;企业资源计划&#xff09;系统可以协调管理生产过程…

SpringCloud Gateway

目录 一、gateway简介二、gateway快速入门2.1 引入依赖2.2 编写启动类2.3 编写基础配置和路由规则 三、断言工厂四、过滤器工厂4.1 路由过滤器的种类4.2 请求头过滤器4.3 默认过滤器 五、全局过滤器5.1 全局过滤器作用5.2 自定义全局过滤器5.3 过滤器执行顺序 六、跨域问题6.1 …

Sailfish OS 移动操作系统

Jolla 是一家曾经致力于开发智能手机和平板电脑的公司&#xff0c;但是这些产品并没有取得成功。后来 Jolla 将重心转向了基于 Linux 的 Sailfish OS&#xff08;旗鱼&#xff09;&#xff0c;并将其应用于现有设备上。Sailfish OS 是由 Jolla 在 MeeGo 基础上开发的移动操作系…

鸿蒙绘制折线图基金走势图

鉴于鸿蒙下一代剥离aosp&#xff0c;对于小公司而言&#xff0c;要么用h5重构&#xff0c;要么等大厂完善工具、等华为出转换工具后跟进&#xff0c;用鸿蒙重新开发一套代码对于一般公司而言成本会大幅增加。但对于广大开发者来说&#xff0c;暂且不论未来鸿蒙发展如何&#xf…

实现跨平台高手必修的课程,玩转Flutter动态化的解决的一些精华部分总结

Flutter作为一种快速、可靠的跨平台移动应用开发框架&#xff0c;在动态化方面也有很多令人兴奋的特性。本文将总结Flutter动态化的一些精华部分&#xff0c;帮助开发者更好地利用这些功能。 正文&#xff1a; 在实现跨平台高手必修的课程中&#xff0c;Flutter动态化是一个不…