扎瓦面试题之 wort 关键字一,这个视频可能有点长,请耐心观看。 yoto 关键字有两个主要作用,第一,确保共享电量在线程间可见。第二, 阻止指令重拍。今天我们主要讲 moneto 关键字如何让共享电量在线成间可见。这是我们上一期讲过的 jmm 内存模型。先看一段程序 定义,共享电量 a 等于三,再开启一个子线程,在子线程中执行死循环, a 不等于三的时候退出循环,然后在主线程中将 a 修改为四。由于主线程和子线程对于共享电量 a 是不可见的,所以当主线成将 a 修改为四的时候,子线程 是不知道的,所以子线程无法停止。一旦将共享变量 a 使用 word 修饰子线程,就能及时感知到 a 的变换, 然后正常停止。 monelogle 是如何做到的呢?再回到 jmm 内存模型,数据进入内存的步骤称为 rat, 而 youtub 修饰的变量进入内存的步骤为模额 tori, 翻译成会边就是会加上 note。 所所有的数据出入内存都要经过总线,而当有落所的变量经过总线时,就被其他 cpu 通过总线绣炭机制绣炭道,这时其他 cpu 会将自己本地内存中的变量修改为失效状态。当本地内存的变量失效后,就只能再从主内存中加载,那么就 能拿到最新的变量值了。这就是 wifi 关键字确保共享变量在线承接可见的原理。下期我们来讲 wifi 关键字阻止指令重排的原理,欢迎关注。
粉丝1.4万获赞4.0万

哈喽哈喽,大家好,我是三七,今天跟大家分享一下 aqs 相关概念。 aqs 是一个用来构建锁和同步器的框架,在勾 uc 包下面,很多锁都是基于 aqs 实现的,今天就跟大家一起来看一看 aqs 的基本原理。 话不多说,点个关注,把头埋地,我们开始上课。 a q s 有两个非常重要的组成部分,第一个组成部分叫做 state, state 是一个 inter 类型的值,它是用 volatile 关键字进行修饰。 volatile 在这里的作用是 state 值,如果发生改变,其他县城可以看到 state 的作用是管理同步变量的状态。 state 的值多少代表枷锁,多少代表解锁, 在不同的子类实现中有不同的含义。我们以最简单的 reantre lock 为例,在 antre lock 中, state 等于零,为共享资源,没有被枷锁。 state 等于一,代表共享资源已经被现成枷锁。 state 大于一,代表共享资源已经被现成。加了多把锁。修改 state 变量的一个方法叫做 compare and set state, 该方法采用 final 关键字进行修饰, final 关键字的作用是禁止此类重写该方法, 该方法呢是用 c s 进行实现的。 c s 的全称叫做 compare and swipe。 需要操作两个值,第一个值为期待值,第二个值为更新值。在操作 时先比较就职是否发生改变,如果没有发生改变才交换新值。 cs 是原子性的,其实他就是一个乐观所,我们看图分析一下 cs 的执行过程。假设有一个共享变量 m, 他的值为零, 线乘一呢?或得到 m 的值等于零后,它想采用 c s 的方式将零改为一, 此时就会判断他的期待值和 m 中的共享变量的值是否一样,如果相等,就说明 m 中共享变量的值没有发生变化,这里都是零,所以可以更改。所以线乘一就将 m 的值改为一,那么在 m 值改为一之前,先乘二获取到 m 的值还是为零?他想 通过 cs 的方式将零改为二,此时他的期待值为零,但是共享变量的值已经成为一,说明 m 已经发生了改变,所以他更改失败,他要重新获得 m 的值。此时呢,他重新获得 m 的值为 e, 他又重新发送了 cas, 将期待值改为一,这时呢,共享变量的值也为一,此时他俩就相等,所以可以更改,就将值更新为二。 a q s。 第二个重要的组成部分是 note 结点, note 结点的数据结构如图所示,其中第一个 inter 值我们介绍原码的时候在具体说明。我们先看一下其他的结构,下面有两个 note 结点,分别为 pril 和 nex。 学过数据结构的同学都知道,这种结构非常像双向列表, pro 指向的是该节点的前驱节点, next 指向的是后继节点。 最后一个是 three 类型的纸,用来存放的是线程。那么 no 的节点到底有什么用呢?我们介绍第一个组成部分 states 的时候,我们知道有些线程是可以拿到锁的,有些线程是无法拿到锁的。当多个线程竞争锁的时候,肯定会有没有竞争到锁的线程, 没有就能弄到锁的线程呢,就会打包成 no 节点,按照创建后时间顺序进行排序后,生成每个线程的前驱节点和后期节点。就像这样,这样呢,就形成了一个双向列表,这就是 a q s 次的等待队列。在了解了这两个组成部分 state 和 note 节点以后,我将使用瑞安 tra lock 这样一个例子来解释一下 a q s 在多个县城同时竞争锁的方向,它是如何维护 state 值和等待队列的。这里啊,因为 a q s 是一个很多锁的一个基础, 他的设计满足很多需求。我们这里举的例子只是他的一种情况,如果有一个共享资源,我们假设现在没有现成去访问这个共享资源,这个时候呢,同步状态 state 值就为零。 线程一想去访问这个共享资源,它采用这个 cas 的方式去修改 state 中的值。因为此时 state 中的值和 cs 中期待的值都为零,所以线程一会修改成功,将 所有的值等于一,此时线乘一就会获得所这个线程送入 cpu 执行逻辑。假设死赖的一在 cpu 中执行逻辑的时候, spread 二也想访问这一段共享资源,它也采用 cas 的方式去修改 state 中的值,此时呢, state 中的值为一,而 cas 期待的值为零,所以 期待的值和 state 中的值不相等,所以他就会修改失败。同理, cid 三也是采用 cs 的方式,他也会修改失败, sos 四也会修改失败,那么这三个线程都会进入 aqs 的等待队列中。我们看一下等待队列,这里的 head 节点,也就是头节点是空的,从第二个节点开始才是我们没有抢到锁的线程, 分别是 thread 二、 thread 三和 thread 四,这三个线上会不断做一个死循环,因为 thread 二的前驱节点是这个头节点, 所以 thread 二不断地去尝试枷锁。因为 thread 三和 thread 四的前驱节点都不是头节点,所以 thread 三和 thread 四 这两个线程不断循环,去判断自己的前驱节点是否为头节点。那么这里 a q s 为什么要这样设计呢?让 sweat 二、 sweat 三、 sweat 四都去尝试加速 就可以了吗?啊?这里呢,是让一个县城去尝试枷锁,是为了减少 c p u 的一个占用率。当 thread 一执行完逻辑去释放资源后,他首先呢将 sweet 的值设置为零。由于 thread 二县城在 不断的去尝试加速,发现 skate 的值已经等于零,所以他就去尝试用 c s 的方式让 skate 的值等于一。到 skate 的值等于一,说明 sky 的二这个线程已经 拿到了数。 sweat 二这个线程就是 cpu 中执行逻辑,此时带有 sweat 二这个线程的 note 节点就要从等待队列中移除, 那么这首 sweat 三就会成为头节点的后继节点。 sweat 三就会不断的去尝试枷锁。当线乘二执行完逻辑后,线乘二就会释放锁, 将 state 的值恢复为零。此时线乘三发现 state 值已经恢复为零,它就采用 c s 的方式尝试将 state 值改为一,修改成功后,说明线乘三已经获得了所,线乘三就会从等待队列中移署。 线乘三的运行逻辑执行完毕后,线乘四会和线乘三一样去尝试加锁,并从等待队列中移除。当线乘四释放锁,运行逻辑执行完毕后, 等待队列中没有了其他的线程,说明 aqs 的加索过程已经完毕。今天给大家分享了 aqs 的基本原理,麻烦大家给我点个关注,让我有更多的动力去创作新的作品,谢谢大家下课!

你有没有遇到过各种诡异的问题?代码写的好好的,可就是不按照预期运行。比如这段代码逻辑很清晰,子线程在循环判断 flag 标识一旦为 false, 子线程会立即退出。 可明明已经在主线程中把 flag 改成 false 了呀,结果子线程就像瞎了一样,还在那傻傻的死循环,根本没有要退出的迹象。百般确认,逻辑确实没有问题, 可跑起来就是不对劲,是不是很邪门?先别慌,作为一个优秀的程序员,咱不信鬼也不信神,遇到事先点根香,啊!不对,是先点根烟压压惊!其实这根本不是啥神秘事件,而是并发编程里的一个非常典型的坑可见性问题。 今天我们就来拆解这个问题,顺便认识一位编程界的大神沃洛特尔,他不仅能解决可见性问题,还能避免指定重排带来的一系列问题。 如果你不清楚这两个概念,请不要划走,我们一一说来。这里是源码世界,用动画带你轻松搞定编程知识,专治各种看不懂。 先说说刚才的子线城为啥会眼瞎,以及啥是可见性问题。来打个比方,想象一下,你和同事用 get 维护同一个项目,你这边咔咔改完代码,还没来得及部署到远程仓库,结果你的同事一看,哎,代码怎么还是旧的呢?问题出在哪呢? 很简单,用过 get 的 都知道,你得先将最新提交推送到远程,然后同时再拉取一下,才能看见最新的改动。 像这种协助场景下,一个人的改动,别人无法立即看到的现象,就是可见性问题的雏形。在 java 的 世界里,为了屏蔽不同硬件的底层差异, jvm 搞了一套规范,叫 java 内存模型, 也就是 j m m。 在 这套规范里,所有的共享变量都存在主内存里,并且每一个县城都有自己的工作内存, 里面放着主内存变量的副本。县城要改一个变量,得先修改自己工作内存里的副本,这时候,主内存和其他县城的工作内存里的值都还是旧值。 如果此时另一个县城恰好来读这个变量,它读到的自然是自己工作内存里的旧版本。不过这只是 jvm 层面的解释。其实问题的根源在硬件那儿。 我们知道 cpu 跑的太快了,内存根本跟不上。为了不让 cpu 干等着,工程师们在 cpu 和内存之间加了多层缓存,每个 cpu 核心都有自己的缓存。那问题来了,对于同一个共享变量,不同核心缓存里的数据不一样,怎么办呢?工程师们自然也意识到了这个问题, 于是他们搞了一个叫缓存一致型协议的东西,专门用来让各个核心的缓存数据保持一致。照理说,有了它,数据就应该一致了呀,不应该有可见性问题了吧? 问题没那么简单,工程师们觉得多级缓存虽然读起来快,但同步起来还是太慢了。举个例子, 一个核心要改某个共享变量,他不光要把结果写到自己的缓存里,还要扯着嗓子喊,嘿,兄弟们,我改了啊,你们手里的那个版本已经作废了,不能再用了,收到请回复。然后他就傻等着, 直到所有的核心都回复收到,已作废,这时候才可以继续干下一件事。对于 cpu 来说,这种等待简直就是浪费时间,于是工程师又在每个核心和缓存之间加了一个斜缓冲区。这下爽了,要写数据,直接扔到斜缓冲区里就行,不用再等了, cpu 可以 继续疯狂的执行后面的指令,效率确实提高了。但问题也来了,数据没有从协缓冲区写入缓存时,不同核心看到的数据仍然是不一样的,这就是可见性问题的硬件根源。聊完可见性,再来说一说另一个坑,指令重排问题。 也就是说,代码实际执行的顺序可能和你写的代码顺序不一样。这事发生在两个阶段,编一期和 cpu 执行期。由于代码的执行顺序一定程度上会影响 cpu 的 执行效率, 所以在编一代码时,为了尽可能的充分利用 cpu 的 性能,可能会根据一些规则适当的调整代码的前后顺序。 而 cpu 执行时,还记得前面说的斜缓冲区吗?可能会出现这种情况, a 等于一的数据还在斜缓冲区里等待时, b 等于二可能已经执行完了,并且数据先一步来到了缓存里,此时从别的核心的视角来看,相当于先执行了 b 等于二,就好像发生了指令重排。不过,无论是哪种重排,在单线程下都是能够保证程序的最终结果是正确的。 但在多县城下可能会出大问题。比如这个例子,主县城负责出售化配置数据,然后设置 over 为处,表示配置已就绪,子县城循环等待,一旦配置就绪,就读取配置数据并执行后续逻辑。 但是如果主县城发生了指令重排,先执行了 over, 等于处子。县城一看配置已经就绪了,立即去读取配置数据,但是配置数据可能还没有初步化完,这时候很可能会发生空置针异常。 好了,问题搞清楚了,一个是看不见,一个是乱排序,怎么解决呢?没错,就是开头我们提到的关键字 volatile。 从语义上说,它有两个作用, 第一,保证可见性,被他修饰的变量一有改动,立马强制同步到主内存,同时其他工作内存中的变量副本会立即失效,需要时必须从主内存重新读取, 从而做到县城间的立即可见。第二,禁止指令重排,它在变量读写前后各加了一道内存屏障,这就像在代码里砌了一道墙,告诉编辑器和 cpu, 墙前面的代码不允许跑到墙后面去,墙后面的代码也不允许跑到墙前面去。 在硬件层面,这个所谓的屏障其实就是一条特殊的指令,比如叉八六架构下的 lock 前缀指令,它会以原子操作,强制把斜缓冲区里的数据刷到缓存里,这就保证了可见性。 由于前一条指令的可见性一定在后一条之前,所以从其他核心的视角来看,也就没有指令重排的效果了。对于硬件层面的解释比较粗糙, 并且不同硬件的实现细节也不一样。但作为 java 程序员,我认为能理解到 jvm 规范这一层,就足够能写出健壮的代码了。底层硬件的事就留给爱卷的大神们在评论区讨论吧,如果你喜欢我的视频,别忘了关注我,下期更精彩!



configuration 它的作用以及解析原理?那这道面试题呢,我相信大部分同学应该是不会的。很多同学觉得 configuration 它的作用呢,就是用来代替我们以前的这个 supreme 点 x mail 的配置文件的,对不对?其实并不全对,怎么说呢,我给农民说一下, 为了本视频的文档,我已经整理好了,并且与往期内容一起会放在了视频的最后面,坚持看完一定对你有帮助。很多同学说恐惧危险,这个注解 又是用来代替我们以前这种 x male 配置配置 方式的这个 spring 点 xml 配置文件,对吧?其实并不全对。为什么这么说呢?我们以前的话,你一定要用 spring 点 xml, 你才能去配置对应的病,对不对? 但是在我们的 drag config 里面啊,哪怕你不用 configuration, 它也是可以去配置病的。来,我给农民演示一下,可能很多同学可能会不信,你看啊,我在这里去给他配置了一个病,对吧?我现在先把 confirmation 我这个注解,我先给他注视掉, 你们看一下啊,我在这里进行运行,我在这个配置类里面,当然这个配置类我现在把这个 reaction 去掉了,对吧?你看这个 rose 啊,是不是一样的就给我创建了,我是不是从容器当中拿到的一个 rose 啊,对不对? 所以说你哪怕没有孔子 creation, 他也是可以去创建病的。好吧,那么我们来说一下,没有孔子 creation 呢,也是可以配置我们的这个病的。当然面试官他可能又会去问你,那没有孔 也可以配置病的话,那加跟不加有什么区别呢?加与不加有什么区别? 有可能面试官呢,他会接着来问你对不对?那这里可能又有很多同学不会了,对不对?那么加了,我在这里稍微说一下,好吧,因为他会牵扯到我们下一个面试机加了孔平威士呢, 它为为我们的,为我们的配置类创建一个 cg live 动态代理,为什么要创建 c g live 动态代理呢?保证用于保证,我们就是配置类中 at b 的方法, 要用它的一个单例,好吧,保证配置类中 add b 的方法 in 的单例,这句话能理解吗? ok, 咱们 看一下,就比方说,我在这里,我在这里又配了一个 user service 的病,那么我在这里呢,去调用了两次 role service, 那么这两次 rose 呢,它实际上去 ios, 它实际上呢是去 ioc 容器当中去找,而不是说去调用两次这个方法去执行两次这个方法当中的代码并不是这样的, 好吧,所以说它创建 cg lab 的作用呢,就是当我们去调用这个方法的时候呢,它就会执行 cg lab 的增强,从而呢帮我们去 ioc 容器当中,帮我们去 get 病,获取当前这个方法对应名字的一个病,这样呢就能保证病的一个单例, ok, 好吧,所以说加了 configuration, 它才能保证被 add 并修饰的方法调用的一个单例,好吧,好,那么这是它的 一个作用啊,创建这个 cg lab 动态代理,那么呢,出现 这样呢,就会会去容,就是 at 并方法的调用,我们刚刚已经说了,对吧,方法的调用呢,就会去容器中,就会通过 啊,通过我们的容器点 get 病进行过去,这样呢就保证了病它的一个单立, 那么这是它的作用,好吧,我们再来说一下它的原理,原理呢也是一样的,在我们创建一个 application, 在我们创建一个 spring 容器的时候, or seven 上下文的时候,会帮我们注册一个解析 ag 类的处理器,我们看一下, 我们看在我们去创建一个 spring 上下文会怎么样?调用 this, 然后呢去帮我们 new 一个并 definitely read, 在这个里面呢,我其实之前的面试题就已经给同学们介绍了这个方法,对吧?他会帮我们注册很多的用于 支支撑 ioc 家的整个过程的一些内置的一些病的组件,在这里呢会去进行注册。 那么之前我们讲到这个 alt y 的注解的原理的时候,就讲到他会注解这个 alt y 的的一个后处理器,对吧?那么同样的会帮我们注册一个配置类的一个后置处理器, 我们可以看一下,这是它的名字,好吧,也是加了 internal, 加了 internal 呢就是内部的一个印图键,这是它的名字啊,我们主要看它的一个类,这个类 呢就叫做 configuration class post process, 好吧,会去注册一个这样的东西,那么它有什么特殊的地方呢?它呢就 它给我们实现了这个冰 factory post process, 好吧,他给我们实现了这个扩展接口,还有和我们的 indefination registry r e g i s t r y poster process 完,我们实现了这两个扩展接口,然后呢再调用 in work, 在调用这个 in work in factory post process 这个方法的时候,这个方法也就是在 调用这个 refresh 这个方法,也就是这个 in work in fact post process。 在这个方法当中呢,就会去解析所有实现了 in factory 兵工厂后置处理器的所有的这个病,好吧,那调用这个方法的时候呢,就会去 就会去帮我们呢去调用这个 page lay 后置处理器的对应的方法, 对应的方法进行进行解析。配置类,好吧,解析配置类,解析配置类,说白了,说白了就是去解析我们的各种注解, 各种各种注解,比如说我们的 at bing 啊, 我们的 at configuration 啊, at import 呀, at component 呀等等等等等等,都是在这里面解析。说白了说白了呢就是注册并 definition。 说白了去解析这些注解呢,说白了就是去注册并给你审,好吧,那么他首先呢会通过好吧去调用他的哪个方法?我们可以看一下啊,看一下这个类他的一个类结构, 我们可以看一下。点错了,我们看啊,它是不是实现了并工厂的后置处理器?是不是实现了并 definition? redrestrate 就是我们并第一的注册器的后置处理器啊?它有什么能力啊?它可以用来动态的去注册我们的并 definition, 对吧? 所以他去我们的这个配置类的 hold 处理去去实现它的一个原因,说白了就是去解析我们各种各样的注解,然后去动态的注册并点个 listen, 所以要实现这个接口,知道吧? ok, 哎,点进去,怎么点不进去呢?稍等一下,卡了。 他呢就是会调用这个 post process in definition registry 这个方法,我们就就不说对应的方法了,好吧,就会调用这个类的它这个方法 进行解析配置,就是去注册 pd ventation, 好吧?然后呢会去调用我们的这个配置类的 解析器的它的方对应的方法, 哎,他的对应的这个 post process 并非的方法, 去干嘛呢?去创建, 去创建 cg live 动态带领, ok, 大概就是这样的一个过程好吗? nice。