Synchronized锁(大妈炒菜例子)

Java中要防止多线程访问同一临界资源时候发生抢夺,进而导致资源数据出错的情况,我们都要在对应的代码块或者方法中加Synchronized关键字,这个关键字,注意不是Java一个方法,只是一个关键字,它就是让多线程在执行到这个方法的时候,一次只允许一条线程访问操作,其它线程都在等待状态。
举例翻译:一个超市做活动,免费提供锅和煤气给顾客自己动手炒菜,中国大妈蜂拥而上,不排队,有的自己的菜还没炒熟,她把别人菜端走了,有的根本没放菜,她也等着端走菜,有的大妈一直往锅里放菜,但是一直没看见自己菜在哪里(被别的大妈捞走了)。这时候大妈们吵起来了,这个骂那个偷菜,那个骂这个不要脸,甚至打起来。
超市保安来了,觉得这样不行,于是拉起了围栏,强制大妈们排队,一个个放自己的菜下去煮。煮熟之后全部捞起端走再到下一个大妈操作。井然有序,再也没发生吵闹打架事件。
这个例子中从一开始的大妈都围在锅旁争抢临界资源,到一个个排队操作临界资源。这样就保证解决了吵架打架的线程冲突问题。但是同时也发现效率会低很多了。
这个保安,就是Synchronized关键字,我们点不进去看,因为它不是Java方法,跟lock()那些锁不同。那么这个保安具体长成什么样,能使得多线程大妈这么听话呢?编译它并且看看字节码源码:

在这里插入图片描述

下面就是激动人心的看字节码环节:

在这里插入图片描述

原来它的是通过monitor监控,并且释放监控,还自动帮我们捕抓异常释放监控,保证不会发生死锁。这跟使用lock()确实非常相识,只不过lock()是我们自己实现的这里是源码帮我们写好的,同时lock()的粒度也会细。
网上都说同步锁同步方法同步代码块效率会很低,其实Synchronized也在不断做优化了。就比如以下优化方法:

在对一个对象加锁时,对象头标记位就会记录锁的类型(轻量级锁,重量级锁,偏向锁,自旋锁等等);这里记住,这里说的类型,这些优化,都是JVM帮我们做的,并不需要自己实现,所以这里主要是做了解,学习别人这种优秀的思考能力和优化方式。

无锁状态:

对象头markword标志位是 01 ,代表了无锁,也就是没有保安监督着大妈们。

轻量级锁:

对象头markword标志位是 00 ,临界资源虽然会被多个线程访问到,但不会发生资源抢夺,在操作时间上都是分开的,就可以用轻量级锁。轻量级锁期间线程CPU执行时间片切换出去再回来后,还是该线程持有目前的锁,于是锁重入继续执行。
例子:就是有的大妈早上来炒菜,有的大妈中午来炒菜,有的大妈晚上来,大家错峰过来炒菜,不会发生争抢炒锅,有的大妈炒着菜忽然出去接个电话,回来看到也还是自己的菜在锅里,所以这就是没有资源抢夺冲突的状态。所以这时候保安就不需要严正以待站在炒锅旁边监督着,因为反正不会发生资源抢夺冲突,那保安就轻松一点,边玩手机边看两眼,确保秩序就行,其余多余的动作(呵斥,站起来,拉起围栏,监视)等就没必要做全套了,节省了保安的时间和精力。

偏向锁:

就是在一段时间内,操作临界资源的都是同一个线程,所以就给这个临界资源做一个倾向于那个操作线程的偏向锁,所以偏向锁是对轻量级锁做的优化,就像上面说的,轻量级锁会有锁重入,也就是再次确认再次获取锁释放锁和CAS判断。但是同一时间假如都是同一个线程在操作,那么就不必要进行锁重入,而是直接操作就可以。所以Markword对象头记录的可以是当前线程ID,下次进来判断如果还是当前线程ID,则不进行锁重入的CAS判断操作。
大妈炒菜例子就是一个大妈炒菜到一半出去了,她回来后还会看看锅里是不是自己的菜,检查一次看看是不是别人的菜,看看有没有刚好碰巧是别人把自己菜端走了,又放回一样的菜,看看是否需要重新加盐加油等等。引入了偏向锁就没那么麻烦,她走之后保安记录下她的线程ID,她回来后保安直接告诉她,这段期间一直都是你的线程ID,没有其他大妈端走你的菜又放回新的菜到你锅里。所以大妈就放心了,根本不进行检查,只需要继续翻炒就行。

锁膨胀:

锁膨胀就是轻量级转化成重量级锁。轻量级锁加锁操作中,假如发现已加锁导致自己的轻量级锁加锁失败,也就是发生了加锁冲突,就会导致锁膨胀为重量级锁。
轻量级锁状态下保安和大妈都很轻松,忽然间一个大妈正准备倒菜入锅炒,发现锅已经有菜并且加锁了,保安就不能玩手机了,马上站起来检查,发现是已经有锁了导致的下一个大妈加锁失败,所以保安马上拉起警报,锁升级,拉起警戒线,呵斥排队,眼睛盯着锅防止大妈拿错别人炒到一半的菜。

重量级锁:

对象头markword标志位是 10 , 重量级锁是轻量级锁的升级,是在多个线程抢夺资源了才进行的。monitor就会严格监控每个线程的获取锁和释放锁的过程。
也就是本来只有一个大妈在早上炒菜的,刚好炒菜那个大妈接电话去了,忽然来多了两个大妈,看见锅里有菜又没人,就准备端走。这时候保安肯定要赶紧收起手机,站起来,跑过去呵斥,指挥她们排队,拉起围栏,等到打完电话的大妈回来继续炒菜,保安就说:这已经不是轻量级锁了,人多了,升级为重量级了,你赶紧炒完给别人用锅。

自旋锁:

锁自旋是对重量级锁的一种优化。就是其它线程会分几次不停询问是否已经释放锁,同时如果回答是则马上进行自己的临界资源操作。如果不是则不再询问,默默等待。
这里举个例子等红灯,如果红灯还有三四秒,谁都不会熄火再启动,因为那样更浪费时间和资源。如果红灯还有五分钟,那谁都会熄火等灯。所以都是出于资源的节省出发点。
大妈炒菜也是同理,还很久才轮到的大妈肯定在吹牛,看抖音看连续剧玩手机,如果还有一两个人就轮到了的大妈,肯定在仔细看自己的菜有没有准备好,想想等一下轮到自己该怎么炒。同时快轮到的大妈也是最心急的,三番五次问别人做好了吗?可以轮到我了吗?问了三四次得到回答都是还没,大妈也就不好意思再继续缠问,就默默等通知。

其他优化:

这就相当于一个开放性问题了,假设你是保安管理员,怎么去优化抢锅炒菜呢?
缩短上锁操作时间:
也就是限定每个线程进行的操作时间尽量缩小,同步代码块中尽量缩小,这也属于细化锁粒度的一个优化。
强制缩短大妈们慢悠悠小火熬汤炖排骨的时间,必须是大火急炒。
细化锁的粒度:
有的大妈抢到锅,才发现自己有的菜没洗干净,鱼肉没刷干净鳞和刺,于是占用着锅慢悠悠刷,保安插手,命令所有大妈占用到锅之前必须把拉拉杂杂的琐碎工作做完,抢到过只能进行一样操作——开始炒菜!不管你的材料洗没洗干净都强制扔下去炒!所以这就是细化了锁的粒度,大妈们纷纷开始在操作临界资源外就对自己的菜进行清洗和其它准备工作,等到轮到自己了,马上扔下去开始炒,大大节省了时间。
粗化锁:
有的大妈抢到锅只是煎一个鸡蛋,再乘起来,再煎一片猪肉,再乘起来,再煎一片年糕,明显的浪费大家的时间,保安就插手,让这个大妈一个大锅划分三片区域,同时煎鸡蛋/猪肉/年糕,同时乘起来,原来需要加锁三次的操作,粗化为一次就执行完,省去了重复加锁的过程,也节省了其他线程的时间。
锁消除:
通过逃逸分析分析出该方法和该临界资源并没有发生逃逸,一直都是该线程调用。那么就进行锁消除。
假设没人跟你抢锅了,也就是别的大妈压根不知道这里有个免费的锅炒菜活动,你就没必要那么匆匆忙忙了,保安也撤离了警戒线,重新坐下来掏出手机刷抖音。剩下最后那个大妈爱怎么操作怎么操作。
进行读写分离:
读写分离其实说到底也是细化锁粒度方面的优化。拆分读写,读不会发生冲突,所以不加锁,写的部分才加锁。
很多大妈排队,不是全部大妈手上都拎着菜去炒的,有的大妈只是贪新鲜排队去看看这个锅怎么操作,是不是那么好用,炒出来的菜香不香,那么商家就可以把贪新鲜看热闹那部分大妈请出来,围在另外一个锅前看销售人员怎么炒,同时给她们一点品尝。这就起到了分流作用。

好了,以前都是我自己对Synchronize的总结和优化总结。就暂时写到这里,鞠躬!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值