注:javap A.class反编译;javap -v/-c A.class查看编译后的指令。
1,synchronized实现原理
1)Java对象头和monitor
image.png
Hotspot虚拟机的对象头:包含Mark Word标记字段和Klass Pointer类型指针(指向类元数据)
Mark Word:对象自身运行时的数据,hashCode、gc标志、gc年龄、同步锁等。
Monitor:每一个锁住的对象,都会和一个monitor关联,MarkWord中的LockWord字段指向monitor的起始地址。Monitor的owner字段存放拥有该锁的线程id
synchronized:修饰方法或者代码块,保证同时只有一个线程处于临界区中,保证了线程对变量访问到可见性和排他性。会引起线程上下文的切换和调度
synchronized多核cpu上:需要sync cpu高速缓存,需要进行内核间缓存一致性通信,代价很大。
2)javap查看synchronized编译后的指令
同步对象通过monitorenter和monitorexit实现
image.png
image.png
同步方法flag上增加ACC_SYNCHRONIZED实现
image.png
3)synchronized修饰实例方法和静态方法
修饰实例方法:只有调用同一个对象方法的多个线程,才会同步。使用不同的对象调用,不会发生同步。
修饰类方法:对该类的Class对象加锁。不同线程访问的是同一个class对象。
2,volatile实现原理
1)防止指令重排。(
image.png指令重排多线程可能导致问题
)
使用"内存屏障"的一组指令实现有序。
volatile加双重校验实现单例
singleton = new Singleton();对象构造过程
1.分配内存地址。2.初始化对象。3.将内存地址赋值给引用。
2)可见性问题。(线程拥有自己的高速缓存区 - 线程工作内存,线程通过工作内存与主存交互。
)
volatile修饰字段后,线程工作内存无效。一个线程修改后,直接立即更新到主存中,其他线程直接从主存中读取。
image.png
3)原子性只能保证单次读/写的原子性
i++实际为load、Increment、store三个操作。
image.png
4)volatile下i++具体过程解析
期望i=12,实际i=11
初始i=10 -> 线程A load变量i到cpu缓存,然后放入寄存器 -> 寄存器中i+1变为11(由于没有直接修改缓存值i,其他线程无感知) -> 线程B走完load、incrment、store流程,刷新i回主存。 -> 线程A中缓存值i=10失效,重新读取内存值i=11 ->线程A执行store,将寄存器中i=11赋值给cpu缓存,并刷新到主存。
3,内存语义对比
1)synchronized与volatile语义
锁的获取与volatile读有相同的语义。线程本地内存无效,共享变量从主存中读取。
锁释放与volatile写有相同的内存语义。锁释放时,线程将本地内存中的共享变量刷新到主存中。
2)ReentrantLock与volatile语义
ReentrantLock的实现基于volatile的state变量
:加锁先读取volatile修饰的state;释放锁时写volatile 修饰的state变量。
3)CAS操作volatile变量,同时具有volatile读和volatile写的内存语义。
网友评论