精华内容
下载资源
问答
  • 面试必备之乐观锁与悲观锁

    万次阅读 多人点赞 2018-07-16 22:34:26
    乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 悲观锁 总是假设最坏的情况...

    本文已经收录进 Github 90k+ Star 的 Java项目 : https://github.com/Snailclimb/JavaGuide 。学 Java/准备 Java 面试 ,首选 JavaGuide。觉得不错的话,点个 star 哦!

    何谓悲观锁与乐观锁

    乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    两种锁的使用场景

    从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

    乐观锁常见的两种实现方式

    乐观锁一般会使用版本号机制或CAS算法实现。

    1. 版本号机制

    一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

    举一个简单的例子:

    假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。

    1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
    2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
    3. 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
    4. 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

    这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

    2. CAS算法

    compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

    • 需要读写的内存值 V
    • 进行比较的值 A
    • 拟写入的新值 B

    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试

    乐观锁的缺点

    ABA 问题是乐观锁一个常见的问题

    1 ABA 问题

    如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

    JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    2 循环时间长开销大

    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

    3 只能保证一个共享变量的原子操作

    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

    CAS与synchronized的使用情景

    简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

    1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
    2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

    补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞竞争切换后继续竞争锁稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

    优质原创PDF资源

    一些计算机基础+Java的原创PDF下载地址: https://pan.baidu.com/s/1H-tR39b9UYHPWRGm1V0-1Q 密码: 8djk

    展开全文
  • 乐观

    千次阅读 2011-11-24 10:06:10
    解释1、乐观,是一种最为积极的性格因素之一。一种生活态度。乐观就是在无论什么情况下,即使再差也保持良好的心态,也相信坏事情总会过去,相信阳光总会再来的心境。   乐观,说白了,就是有个“美好的信”。...
    解释1、乐观,是一种最为积极的性格因素之一。一种生活态度。乐观就是在无论什么情况下,即使再差也保持良好的心态,也相信坏事情总会过去,相信阳光总会再来的心境。 
    
      乐观,说白了,就是有个“美好的信”。总是相信一些好的东西。一个什么都不信的人,再怎么笑都不是一个乐观的人。当然,有人相信美好,有人相信丑恶。而这种美好,大抵指的是“真善美” 

      体育和运动可以增进人体的健康和人的乐观情绪,而乐观情绪却是长寿的一项必要条件。

    解释2、每个人都应该十分的乐观,才会过得很快乐。 
      (1)善于观察。《史记·货殖列传》:“当 魏文侯时, 李克务尽地力,而 白圭乐观时变,故人弃我取,人取我与。” 
      (2)喜欢观看。 唐 韩愈《送王秀才序》:“自 孔子没,羣弟子莫不有书,独 孟轲氏之传得其宗,故吾少而乐观焉。” 清 叶廷琯《吹网录·三河县辽碑》:“ 辽 碑世既无多,此尤后出,嗜古者当亦乐观也。” 鲁迅《书信集·致黄源》:“有图有说,必为读者所乐观。” 
      (3)精神愉快,对事物的发展充满信心。与“悲观”相对。 苏曼殊《碎簪记》:“念天地之悠悠,惟有强颜欢笑,情郁于中,而外貌矫为乐观。” 巴金《秋》八:“她永远保持着她的乐观,她的愉快的心情,她的勇气,她的欢笑。” 艾青《火把》诗:“我很乐观,因为感伤并不能把我们的命运改变。”

    乐观名言1
      ●风力掀天浪打头,只须一笑不须愁(宋·杨万里) 
      ●视困难为常规,以付出为快乐 
      ●纵声欢唱的人会把灾祸和不幸吓走(西班牙) 
      ●如果人是乐观的,一切都有抵抗,一切都能抵抗,一切都会增强抵抗力(瞿秋白) 
      ●生命苦短,便这既不能阻止我们享受生活的乐趣,也不会使我们因其充满艰辛而庆幸其短暂(沃维纳格) 
      ●乐观的人,牙齿落完也不老(维吾尔族) 
      ●乐观使人长寿(缅甸) 
      ●落朝总有涨潮时 
      ●百日阴雨总有一朝晴 
      ●任凭雨注,总有天晴时(非洲) 
      ●如果冬天来了,春天还会远吗?(雪莱) (英国) 
      ●母象要很久才生个象仔,但它终究是会生的(越南) 
      ●东方不亮西方亮,哪里有四方黑沉沉 
      ●缺了日头有月头 
      ●车到山前必有路,船到桥头自然直 
      ●天塌正好抓云雀(英国) 
      ●天塌下来有高个子顶 
      ●可以动摇,但不会被覆灭(巴黎市徽上的名言) 
      ●不怕荒年,只怕靠天 
      ●天有无情灾,人有回天力 
      ●兵强胜人,人强胜天〈逸周书〉 
      ●人以巧胜天(清·林和靖) 
      ●人事可以补天工 
      ●人谋可以夺天算 
      ●人诚务胜乎天(唐·刘禹锡) 
      ●天能生物,不能辨物,地能载人,不能治人〈荀子〉 
      ●自然者天地,主持者人(清·王夫之) 
      ●天不能司人之命(清·洪亮吉)
    乐观名言2
      1.一个人也许会相信许多废话,却依然能以一种合理而快乐的方式安排他的日常工作。——诺曼·道格拉斯 
      2.我们曾经为欢乐而斗争,我们将要为欢乐而死。因此,悲哀永远不要同我们的名字连在一起。——伏契克 
      3.乐人之乐,人亦乐其乐;忧人之忧,人亦忧其忧。——白居易 
      4.一个人的特色就是他存在的价值,不要勉强自己去学别人,而要发挥自己的特长。这样不但自己觉得快乐,对社会人群也更容易有真正的贡献。——罗兰 
      5.各人有各人理想的乐园,有自己所乐于安享的世界,朝自己所乐于追求的方向去追求,就是你一生的道路,不必抱怨环境,也无须艳羡别人。——罗兰 
      6.一个人如能让自己经常维持像孩子一般纯洁的心灵,用乐观的心情做事,用善良的心肠待人,光明坦白,他的人生一定比别人快乐得多。——罗兰 
      7.开朗的性格不仅可以使自己经常保持心情的愉快,而且可以感染你周围的人们,使他们也觉得人生充满了和谐与光明。——罗兰 
      8.快乐应该是美德的伴侣。——巴尔德斯 
      9.记住要做一些对大众有利益的事,令大家生欢喜心。只有施与快乐,就会得到快乐的果报。——方海权 
      10.当我偶尔对人生失望,对自己过分关心的时候,我也会沮丧,也会悄悄的怨几句老天爷,可是一想起自己已经有的一切,便马上纠正自己的心情,不再怨叹,高高兴兴的活下去。不但如此,我也喜欢把快乐当成一种传染病,每天将它感染给我所接触的社会和人群 
      11.不应该迫求一切种类的快乐,应该只追求高尚的快乐。 —— 德谟克利特 
      12.真正的快乐是内在的,它只有在人类的心灵里才能发现。 —— 布雷默 
      13.所谓内心的快乐,是一个人过着健全的正常的和谐的生活所感到的快乐。 —— 罗曼·罗兰 
      14.乐观是一种快乐的享受 —— 莎士比亚
    乐观名言3
      内心的欢乐是一个人过着健全的、正常的、和谐的生活所感到的喜悦。 
      ——罗曼·罗兰 
      人们需要快乐,就像需要衣服一样。 
      ——玛格瑞特·科利尔·格雷厄姆 
      人生要有意义只有发扬生命,快乐就是发扬生命的最好方法。 
      ——张闻天 
      愉快的笑声,是精神健康的可靠标志。 
      真正的快乐是对生活的乐观,对工作的愉快,对事业的兴奋。 
      ——爱因斯坦 
      面对压力、挫折、困难、失败,都应该有乐观的心态。那是重整旗鼓所必须的素质。 
      ——方海权 
      最明亮的欢乐火焰大概是由意外的火花点燃的。人生道路上不时散发出芳香的花朵,也是由偶然落下的种子自然生长出来的。 
      ——塞·约翰逊【英】 
      最幸福的似乎是那些并无特别原因而快乐的人,他们仅仅因快乐而快乐。 
      ——威廉姆.拉尔夫.英奇【美】 
      快乐并不需要下流或肉欲。往昔的智者们都认为只有智性的快乐最令人满足而且最能持久。 
      ——毛姆 
      快乐不在于事情,而在于我们自己。 
      ——理查德·瓦格纳【美】 
      快乐的秘诀是:让兴趣尽可能地扩张,对人对物的反应尽可能出自善意而不是恶意的兴趣。 
      ——罗素 
      快乐既然是人类和兽类所共同追求的东西,所以从某种意义上说,它就是最高的善。 
      ——亚里士多德 
      快乐是一种奢侈。若要品尝它,绝不可缺的条件是心无不安。心若不安——即使稍受威胁,快乐就立刻烟消支散。 
      ——司汤达 
      如果人是乐观的,一切都有抵抗,一切都能抵抗,一切都会增强抵抗力(瞿秋白) 
      生命苦短,便这既不能阻止我们享受生活的乐趣,也不会使我们因其充满艰辛而庆幸其短暂(沃维纳格) 
      碰到最危险的时候,我都必须往它们幽默的一面看,并且笑一笑,理由──惟一的理由──就在这里。(安妮) 
      当生活像一首歌那样轻快流畅时,笑颜常开乃易事;而在一切事都不妙时仍能微笑的人,才活得有价值。 ——威尔科克斯【德】

    
    展开全文
  • 悲观锁和乐观锁的使用

    万次阅读 多人点赞 2019-07-31 18:41:47
    2、乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,期间该数据可以随便被其他人读取,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以...

    1、悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
    最常用的就是 select … for update,它是一种行锁,会把select出来的结果行锁住,在本事务提交或者回滚之前,不允许其他事务对这些行做update、delete、for update操作。

    2、乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,期间该数据可以随便被其他人读取,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
    版本号机制是乐观锁最常用的方式,就是在表中增加一个版本号的字段,更新前先查一遍获取版本号,再作为更新语句的where条件进行更新,如果数据在获取版本号之后,在更新之前已经改变了,那就会更新失败,因为最后更新了0条数据,java后台拿到更新数如果为0,则说明更新失败,出现了并发问题,然后做具体的处理。
    例如有两个人同时对某条数据做修改,过程如下:
    操作员A操作如下:
    select id, balance, version from table where id=“1”;
    查询结果:id=1, balance=1000, version=1

    update table set balance=balance+100, version=version+1 where id=“1” and version=1;
    执行后,返回的更新结果是1,说明更新了一条,数据库里的结果是:id=1, balance=1100, version=2

    操作员B操作如下:
    select id, balance, version from table where id=“1”;
    查询结果:id=1, balance=1000, version=1, 说明操作员A还没修改。

    update table set balance=balance-50, version=version+1 where id=“1” and version=1 ;
    查的时候,操作员A还没修改,当要更新时,操作员A已经先修改成功,所以数据库里实际值是id=1, balance=1100, version=2,
    操作员B也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时查不到where id=“1” and version=1 的数据,
    所以update就失败了,执行结果是0,说明没有对任何数据更新成功。

    现在再去查一下,结果还是操作员A操作完成之后的结果
    select id, balance, version from table where id=“1”;
    查询结果:id=1, balance=1100, version=2

    以上是自己实现版本号机制的原理,真正使用的版本号机制是数据库本身带有的机制,一旦发现更新的版本号不是最新的就会被驳回。

    展开全文
  • 我今天就简单聊一下乐观锁和悲观锁,他们对应的实现 CAS ,Synchronized,ReentrantLock 正文 一个120斤一身黑的小伙子走了进来,看到他微微发福的面容,看来是最近疫情伙食好运动少的结果,他难道就是今天的面试官...

    前言

    关于线程安全一提到可能就是加锁,在面试中也是面试官百问不厌的考察点,往往能看出面试者的基本功和是否对线程安全有自己的思考。

    那锁本身是怎么去实现的呢?又有哪些加锁的方式呢?

    我今天就简单聊一下乐观锁和悲观锁,他们对应的实现 CAS ,Synchronized,ReentrantLock

    正文

    一个120斤一身黑的小伙子走了进来,看到他微微发福的面容,看来是最近疫情伙食好运动少的结果,他难道就是今天的面试官渣渣丙?

    等等难道是他?前几天刷B站看到的不会是他吧!!!

    是的我已经开始把面试系列做成视频了,以后会有各种级别的面试,从大学生到阿里P7+的面试,还有阿里,拼多多,美团,字节风格的面试我也都约好人了,就差时间了,大家可以去B站搜:三太子敖丙 观看

    我也不多跟你BB了,我们直接开始好不好,你能跟我聊一下CAS么?

    CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。

    CAS 是怎么实现线程安全的?

    线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值,操作的时候比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。

    举个栗子:现在一个线程要修改数据库的name,修改前我会先去数据库查name的值,发现name=“帅丙”,拿到值了,我们准备修改成name=“三歪”,在修改之前我们判断一下,原来的name是不是等于“帅丙”,如果被其他线程修改就会发现name不等于“帅丙”,我们就不进行操作,如果原来的值还是帅丙,我们就把name修改为“三歪”,至此,一个流程就结束了。

    有点懵?理一下停下来理一下思路。

    Tip:比较+更新 整体是一个原子操作,当然这个流程还是有问题的,我下面会提到。

    他是乐观锁的一种实现,就是说认为数据总是不会被更改,我是乐观的仔,每次我都觉得你不会渣我,差不多是这个意思。

    你这个栗子不错,他存在什么问题呢?

    有,当然是有问题的,我也刚好想提到。

    你们看图发现没,要是结果一直就一直循环了,CUP开销是个问题,还有ABA问题和只能保证一个共享变量原子操作的问题。

    你能分别介绍一下么?

    好的,我先介绍一下ABA这个问题,直接口述可能有点抽象,我画图解释一下:

    看到问题所在没,我说一下顺序:

    1. 线程1读取了数据A
    2. 线程2读取了数据A
    3. 线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B
    4. 线程3读取了数据B
    5. 线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A
    6. 线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值

    懂了么,我尽可能的幼儿园化了,在这个过程中任何线程都没做错什么,但是值被改变了,线程1却没有办法发现,其实这样的情况出现对结果本身是没有什么影响的,但是我们还是要防范,怎么防范我下面会提到。

    循环时间长开销大的问题

    是因为CAS操作长时间不成功的话,会导致一直自旋,相当于死循环了,CPU的压力会很大。

    只能保证一个共享变量的原子操作

    CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了,JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。

    我还记得你之前说在JUC包下的原子类也是通过这个实现的,能举个栗子么?

    那我就拿AtomicInteger举例,他的自增函数incrementAndGet()就是这样实现的,其中就有大量循环判断的过程,直到符合条件才成功。

    大概意思就是循环判断给定偏移量是否等于内存中的偏移量,直到成功才退出,看到do while的循环没。

    乐观锁在项目开发中的实践,有么?

    有的就比如我们在很多订单表,流水表,为了防止并发问题,就会加入CAS的校验过程,保证了线程的安全,但是看场景使用,并不是适用所有场景,他的优点缺点都很明显。

    那开发过程中ABA你们是怎么保证的?

    加标志位,例如搞个自增的字段,操作一次就自增加一,或者搞个时间戳,比较时间戳的值。

    举个栗子:现在我们去要求操作数据库,根据CAS的原则我们本来只需要查询原本的值就好了,现在我们一同查出他的标志位版本字段vision。

    之前不能防止ABA的正常修改:

    update table set value = newValue where value = #{oldValue}
    //oldValue就是我们执行前查询出来的值 

    带版本号能防止ABA的修改:

    update table set value = newValue ,vision = vision + 1 where value = #{oldValue} and vision = #{vision} 
    // 判断原来的值和版本号是否匹配,中间有别的线程修改,值可能相等,但是版本号100%不一样

    除了版本号,像什么时间戳,还有JUC工具包里面也提供了这样的类,想要扩展的小伙伴可以去了解一下。

    聊一下悲观锁?

    悲观锁从宏观的角度讲就是,他是个渣男,你认为他每次都会渣你,所以你每次都提防着他。

    我们先聊下JVM层面的synchronized:

    synchronized加锁,synchronized 是最常用的线程同步手段之一,上面提到的CAS是乐观锁的实现,synchronized就是悲观锁了。

    它是如何保证同一时刻只有一个线程可以进入临界区呢?

    synchronized,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。

    我分别从他对对象、方法和代码块三方面加锁,去介绍他怎么保证线程安全的:

    • synchronized 对对象进行加锁,在 JVM 中,对象在内存中分为三块区域:对象头(Header)、实例数据(Instance
      Data)和对齐填充(Padding)。

    • 对象头:我们以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

      • Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
      • Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

      你可以看到在对象头中保存了锁标志位和指向 monitor 对象的起始地址,如下图所示,右侧就是对象对应的 Monitor 对象。

      当 Monitor 被某个线程持有后,就会处于锁定状态,如图中的 Owner 部分,会指向持有 Monitor 对象的线程。

      另外 Monitor 中还有两个队列分别是EntryList和WaitList,主要是用来存放进入及等待获取锁的线程。

      如果线程进入,则得到当前对象锁,那么别的线程在该类所有对象上的任何操作都不能进行。

    在对象级使用锁通常是一种比较粗糙的方法,为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?

    如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。

    由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:

    java class FineGrainLock{  MyMemberClassx,y;  Object xlock = new Object(), ylock = newObject();  public void foo(){   synchronized(xlock){   //accessxhere    }   //dosomethinghere-butdon'tusesharedresources    synchronized(ylock){    //accessyhere    }  } public void bar(){    synchronized(this){    //accessbothxandyhere  }  //dosomethinghere-butdon'tusesharedresources } }

    • synchronized 应用在方法上时,在字节码中是通过方法的 ACC_SYNCHRONIZED 标志来实现的。

      我反编译了一小段代码,我们可以看一下我加锁了一个方法,在字节码长啥样,flags字段瞩目:

      javascript synchronized void test(); descriptor: ()V flags: ACC_SYNCHRONIZED Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Ljvm/ClassCompile;

    反正其他线程进这个方法就看看是否有这个标志位,有就代表有别的仔拥有了他,你就别碰了。

    • synchronized 应用在同步块上时,在字节码中是通过 monitorenter 和 monitorexit 实现的。

      每个对象都会与一个monitor相关联,当某个monitor被拥有之后就会被锁住,当线程执行到monitorenter指令时,就会去尝试获得对应的monitor。

      步骤如下:

      1. 每个monitor维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为 1 。
        • 当同一个线程再次获得该monitor的时候,计数器再次自增;
      • 当不同线程想要获得该monitor的时候,就会被阻塞。
      1. 当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。

        当计数器为0的时候,monitor将被释放,其他线程便可以获得monitor。

        同样看一下反编译后的一段锁定代码块的结果:

        public void syncTask();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=3, locals=3, args_size=1
               0: aload_0
               1: dup
               2: astore_1
               3: monitorenter  //注意此处,进入同步方法
               4: aload_0
               5: dup
               6: getfield      #2             // Field i:I
               9: iconst_1
              10: iadd
              11: putfield      #2            // Field i:I
              14: aload_1
              15: monitorexit   //注意此处,退出同步方法
              16: goto          24
              19: astore_2
              20: aload_1
              21: monitorexit //注意此处,退出同步方法
              22: aload_2
            23: athrow
              24return
            Exception table:
            //省略其他字节码.......

      小结:

      同步方法和同步代码块底层都是通过monitor来实现同步的。

      两者的区别:同步方式是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现,同步代码块是通过monitorenter和monitorexit来实现。

      我们知道了每个对象都与一个monitor相关联,而monitor可以被线程拥有或释放。

      🐂,小伙子我只能说,你确实有点东西,以前我们一直锁synchronized是重量级的锁,为啥现在都不提了?

      在多线程并发编程中 synchronized 一直是元老级角色,很多人都会称呼它为重量级锁。

      但是,随着 Java SE 1.6 对 synchronized 进行了各种优化之后,有些情况下它就并不那么重,Java SE 1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。

      针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。

      Tip:本来锁升级的过程我是搞了个贼详细贼复杂的图,但是我发现不便于理解,我就幼儿园化了,所以就有了个简单版本的,先看下复杂版本的:

      幼儿园版本:

      看到这你如果还想白嫖,我劝你善良,万水千山总是情,不要白嫖行不行?点个赞再走哈哈。

      对了锁只能升级,不能降级。

      还有其他的同步手段么?

      ReentrantLock但是在介绍这玩意之前,我觉得我有必要先介绍AQS(AbstractQueuedSynchronizer)。

      AQS:也就是队列同步器,这是实现 ReentrantLock 的基础。

      AQS 有一个 state 标记位,值为1 时表示有线程占用,其他线程需要进入到同步队列等待,同步队列是一个双向链表。

      当获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。

      当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。

      ReentrantLock 就是基于 AQS 实现的,如下图所示,ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。

      和 ReentrantLock 实现方式类似,Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是独占锁,Semaphore 是共享锁。

      从图中可以看到,ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。

      它有公平锁FairSync和非公平锁NonfairSync两个子类。

      ReentrantLock默认使用非公平锁,也可以通过构造器来显示的指定使用公平锁。

      相关资料

      Tip:本来这一栏有很多我准备的资料的,但是都是外链,或者不合适的分享方式,博客的运营小姐姐提醒了我,所以大家去公众号回复【资料】好了。

      技术总结

      锁其实有很多,我这里只是简单的介绍了一下乐观锁和悲观锁,后面还会有,自旋锁,自适应自旋,公平锁,非公平锁,可重入(文中提到的都是可重入),不可重入锁,共享锁,排他锁等。

      多去了解他们的用法,多去深究他们的原理以及实现,我后面会持续更新多线程方面的知识点。

      Tip:这是我新的技术总结方式,以后会慢慢完善,如果被博客平台二压公众号回复【多线程】获取。

      参考:《Java高并发编程》、《美团技术团队锁的思考》、《拉钩张雷java32个考点》

      絮叨

      我不断的尝试新的文章风格,我也把絮叨环节放到了最后就是给大家一个好的阅读体验,有建议随时提哟,新的技术总结方式如何?

      还记得我帮公司内推么,我收到300封简历,但是我放到系统的只有13份,不是我想吐槽大家,是真的得用心点啊,叫我内推发个邮件,简历都不发什么鬼,简历总共才100个字又是什么鬼。。。

      周末出一期视频说一下简历的问题,真的为你们春招担心啊仔。。。。

      絮叨

      另外,敖丙把自己的面试文章整理成了一本电子书,共 1630页!目录如下

      现在免费送给大家,在我的公众号三太子敖丙回复 【888】 即可获取。

      我是敖丙,一个在互联网苟且偷生的程序员。

      你知道的越多,你不知道的越多人才们的 【三连】 就是丙丙创作的最大动力,我们下期见!

      注:如果本篇博客有任何错误和建议,欢迎人才们留言!


      文章持续更新,可以微信搜索「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub https://github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

    展开全文
  • 乐观

    2021-03-04 08:19:29
    乐观锁 (一)什么是乐观乐观锁是为了解决线程冲突情况下脏读,幻读等,乐观锁就是为了解决这个问题而产生的;乐观锁默认情况下认为数据是不会发生冲突的,所以在数据进行提交更新的时候,才会正式对数据的冲突...
  • 悲观锁和乐观

    万次阅读 2018-07-06 13:53:53
    悲观锁和乐观锁 是并发情境下的两种设计思想, 它们的主要区别在于:悲观锁则认为肯定会发生并发问题, 要么我等着, 要么就让别人等;乐观锁认为当前发生并发的可能性不大, 我先试试, 不行的话再说.一般只有在高并发下...
  • 乐观锁以及乐观锁的实现

    万次阅读 多人点赞 2018-08-10 16:38:45
    乐观锁介绍: 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误...
  • 乐观主义者对他们的未来抱有积极的期望,一些人认为这种期望会带来社会领域的优势(Carver,Scheier和Segerstrom,2010年)。 不幸的是,支持这一观点的研究很少,并且遭受这样的困惑:乐观主义者的自我报告反映了...
  • 乐观主义BLOG网页模板

    2021-01-14 10:29:53
    乐观主义BLOG网页模板
  • MyBatis乐观锁插件3.0,全面升级,更加复杂,更加强大 旧版本MyBatis乐观锁插件1.x请移步至wiki文档: MyBatis乐观储物柜插件 文档: 开始... <groupId>com.github.dreamroute</groupId> <artifactId>locker-...
  • 乐观一些

    千次阅读 2021-06-10 09:10:13
    财富多少和过好自己的人生关系不大,但是乐观主义练就的人生观,价值观却是主要的行进框架。 人生的每个阶段不同,随着阅历的增加,造就的视野,格局,处事态度,完全不一样。我的理念,对人多份宽,对己少份容。 有...
  • 悲观锁与乐观锁的实现(详情图解)

    万次阅读 多人点赞 2020-05-27 10:26:59
    在了解悲观锁和乐观锁之前,我们先了解一下什么是锁,为什么要用到锁? 技术来源于生活,锁不仅在程序中存在,在现实中我们也随处可见,例如我们上下班打卡的指纹锁,保险柜上的密码锁,以及我们我们登录的用户名...
  • 初中语文文摘历史乐观
  • 乐观指数测评.doc

    2021-08-11 15:13:38
    乐观指数测评.doc
  • 乐观锁:顾名思义,思想十分乐观,总是认为不会出现问题,无论什么都不去上锁!如果出现了问题,就再更新测试 悲观锁:顾明思义,思想十分悲观,总是认为总会出现问题,无论什么都去上锁!再去操作 我们主要来讲一下乐观锁...
  • 乐观锁详解

    千次阅读 2019-04-11 00:09:32
    乐观锁: 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型...
  • 乐观锁源码
  • 乐观派正好相反。虽然看到前景中有暗淡处,但他们却有前进的动力。所以在项目构思和开始阶段,我会借用乐观派的力量,让他们当牵头人。但是,当这种构想进入具体计划时,再全部委托乐观派就很危险。因为乐观派的动力...
  • 数据库加锁,乐观锁、悲观锁

    万次阅读 2019-08-23 16:09:35
    文章目录一、概念悲观锁(Pessimistic Lock)乐观锁(Optimistic Lock)使用场景二、具体实现方式sql层面Django层面 一、概念 悲观锁(Pessimistic Lock) 每次获取数据的时候,都会担心数据被修改,所以每次获取...
  • 乐观锁测试代码

    2014-10-23 16:45:25
    乐观锁测试
  • Official:trade_mark:乐观主义教程 本教程介绍了在以太坊上开发应用程序的过程。 我们将带您完成编译,测试和部署智能合约的过程。 本教程旨在强调以太坊和乐观以太坊之间的异同。 为此,我们已经设置了教程,以便...
  • 乐观锁 因为热爱,所以拼搏。 –RuiDer 前导必备 Java并发锁的含义 悲观锁 数据库 高并发 高并发简单理解就是在服务器中,成千上完个客户端在同一时间内发 起对服务器端的请求,包括数据的请求,...
  • Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁...
  • redis乐观

    2017-09-19 10:49:25
    redis乐观

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 187,089
精华内容 74,835
关键字:

乐观