java syncnize_面试必考:Java线程同步之Syncronized关键字

56faf2a7a5d7ff46e1e55305ebcfba9f.png

前言

之前我们通过volatiile关键字修饰一个变量i,可以使得在A线程中修改这个变量的副本之后能够立即刷新回内存,并且其他线程在读取之前必须要从主内存中读取这个值,从而达到了简单的线程间同步数据的目的

但是我们也看到这个机制并不是完整的,当访问的变量值依赖于这个变量的前值的时候,这个机制会失效,也就是volatile只解决变量的内存可见性,但是并没有解决原子性。

今天就来看看一个最常用的解决线程间同步的机制以及其背后的原理。

问题

继续我们之前的代码

961c0020a0468d67d147e5d2792f59d2.png

当多个线程访问PlainSeller的实例时,ticket最终的值并不等于increment的调用次数,也就是说如果有10个线程,每个线程调用了一次increment,按理说ticket的值应该是10,但是结果中有时候会出现比10小的情况。

解决

56df8342d588f11e59f31d2de3209d94.png

只需要在increment方法前面加上synchronized关键字即可,我们大白话一下这里发生了什么。

当CPU在执行指令的时候,当遇到了synchronized修饰的方法时会去全局找一把钥匙,如果这个钥匙能够拿到,则能够继续执行,如果不能拿到,则需要等待,这样就确保了在同一个时刻只有一个线程在执行方法体中的指令,也就不会产生同时访问一个变量导致的主内存与线程本地缓存中值不一致的现象了。

如果方法中指令很多,但只有很小一部分代码在同时访问临界资源的时候,这个时候在方法上加synchronized就会很影响效率,因为不需要同步的指令也需要等待拿到钥匙之后才能执行,这个时候我们可以在代码块上加synchronized关键字,如下:

906a09ae220b7bd951d11512028e0aff.png

原理

首先看下两个increment的JVM指令:

SyncronizedSeller

ceb4512c1ae56f13f143cc6a03b67d63.png

PlainSeller

18c72d1c19a909a64fb3d1013fe66a52.png

它俩唯一的区别就是flags上增加了ACC_SYNCHRONIZED位,区别并不明显,看不出来有什么差异。

我们再来看看对应的汇编指令,贴出synchronized部分指令如下:

cd094a274abdf373a34e884adea5a124.png

这里跟不加synchronized关键字的区别在于多了很多 lock cmpxchg 指令。

那我们看一看这个指令是干啥的。

先看cmpxchg,这个指令是比较然后交换的意思,那么比较什么交换什么呢?简单说,多线程中有个叫CAS的原理或者算法,通过这个算法,可以让两个线程同步的访问同一个变量从而达到消除上述bug的作用,在x86指令集里面,CAS原理就是通过cmpxchg这条指令实现的,并且在多核CPU上这条指令需要加上lock前缀,所以就有了lock cmpxchg指令,syncronized通过这条指令的实现,从而达到了同步访问变量的目的。

那么这里就留下了一个坑:CAS原理。

小结

今天我们通过synchronized关键字解决了多个线程同时访问临界资源的时候带来的内存原子性问题,我们也通过背后的JVM指令和汇编指令了解其背后的原理,接下来我们继续看看还有哪些方法能够解决内存原子性问题。如果大家可以给通过crazy042438这个v号问我吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值