大家好,欢迎来到IT知识分享网。
上一篇:Java并发编程(十八)指令乱序机制及预测执行
一、synchronized如何保证原子性
synchronized保证原子性其实底层是通过加锁来保证的,在进入加锁代码块的时候加一个monitorenter的指令,然后针对锁对象关联的monitor累加加锁计数器,同时标识自己这个线程加了锁;
通过monitor里的加锁计数器可以实现可重入的加锁;
在出锁代码块的时候,加一个monitorexit的指令,然后递减锁计数器,如果锁计数为0,就会标志当前线程不持有锁,释放锁;
然后wait和notify关键字的实现也是依托于monitor实现的,在线程执行wait之后,自己会加入一个waitset中等待唤醒获取锁,notifyall操作会从monitor的waitset中唤醒所有的线程,让他们竞争获取锁;

- Header(对象头)
1、自身运行时的数据(Mark Word)

(根据系统虚拟机大小不同其大小不同,32位虚拟机数据大小为32位,64位系统虚拟机数据大小为64位)
存储内容:
哈希值(hashCode()方法是native)
GC分代年龄(为分代收集算法所服务 分代好处:针对各个年龄代特点,选择适当的垃圾收集算法。)
锁状态标志,标识是否加锁,加锁了几次;
在Mark Word里就有一个指针,是指向了这个对象实例关联的monitor的地址,这个monitor是c++实现的,不是java实现的。这个monitor实际上是c++实现的一个ObjectMonitor对象,里面包含了一个_owner指针,指向了持有锁的线程。
ObjectMonitor里还有一个entrylist,想要加锁的线程全部先进入这个entrylist等待获取机会尝试加锁,实际有机会加锁的线程,就会设置_owner指针指向自己,然后对_count计数器累加1次;
偏向线程ID
偏向时间戳
2、类型指针(Class Metadata Address)
对象指向类的元数据的指针,虚拟机通过这个指针来确定对象是哪一个类的实例,并不是所有jvm实现都必须在对象数据上保留类型指针
3、作为普通对象的对象头结构只有以上两种,但如果对象是一个数组,则对象头中还包含记录数组长度的数据
synchronized如何保证可见性
Load屏障的作用是执行refresh处理器缓存的操作,说白了就是对别的处理器更新过的变量,从其他处理器的高速缓存(或者主内存)加载数据到自己的高速缓存来,确保自己看到的是最新的数据。
Store屏障的作用是执行flush处理器缓存的操作,说白了就是把自己当前处理器更新的变量的值,都刷新到高速缓存(或者主内存)里去
在monitorexit指令之后,会有一个Store屏障,让线程把自己在同步代码块里修改的变量的值都执行flush处理器缓存的操作,刷到高速缓存(或者主内存)里去;然后在monitorenter指令之后会加一个Load屏障,执行refresh处理器缓存的操作,把别的处理器修改过的最新值加载到自己高速缓存里来;
总结:通过Load屏障和Store屏障,就可以让synchronized保证可见性。
synchronized如何保证有序性
在monitorenter指令之后,Load屏障之后,会加一个Acquire屏障,这个屏障的作用是禁止读操作和读写操作之间发生指令重排序。在monitorexit指令之前,会加一个Release屏障,这个屏障的作用是禁止写操作和读写操作之间发生重排序。
所以说,通过 Acquire屏障和Release屏障,就可以让synchronzied保证有序性,只有synchronized内部的指令可以重排序,但是绝对不会跟外部的指令发生重排序。
总结
- 原子性:加锁和释放锁,ObjectMonitor;
- 可见性:加了Load屏障和Store屏障,释放锁flush数据,加锁会refresh数据;
- 有序性:Acquire屏障和Release屏障,保证同步代码块内部的指令可以重排,但是同步代码块内部的指令和外面的指令是不能重排的;
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/176809.html