java并发的底层实现
1.java代码在编译后会变成java字节码,字节码被类加载器加载到jvm里,jvm执行字节码,最终转化为汇编指令在cpu上执行
2.volatile和synchronized解释
volatile和synchronized在并发中都有很重的作用,volatile是一个轻量级的synchronized
volatile保证了线程的可见性,当一个变量声明成volatile时,java内存模型确保所有线程可以看到变量值是一致的,通俗一点就是一个线程对这个变量的修改会立即同步到共享内存中,另一个线程能立即读取到最新到值(注意它只能保证新数据立即可见,但是不能解决ABA问题)
volatile如何实现以及如何保证可见性到呢?
volatile修饰的变量,在lock前缀执行在进行写操作的多核cpu做了两件事情
1.将当前处理器的缓存行(cpu高速缓存l1,l2,l3)的数据写回到了系统内存
2.这个写回内存的操作会使其他cpu里存储了该变量的内存地址的数据无效
(1)为了更高效,cpu通常都不直接和内存进行通信,而是先将内存中的数据读取到cpu的缓存行中,在cpu的本地缓存中进行直接数据读写操作,数据何时更新主内存是不定期的,不确定的
(2)如果声明了volatile的变量,jvm就会向cpu发送一条lock前缀指令,将这个变量数据从cpu的缓存行中写入系统内存(共享内存)中!
(3)但是还是有一个问题,那就是其他处理器并不知道内存被 刷新了,其他cpu缓存行中还是旧值,还是有问题!
最初computer是使用的总线加锁,但是锁的粒度太大现在都不用了
(总线: 内存条和cpu怎么交互数据, 总线就是cpu和主板上的条状的排线,通过开关控制,cpu对总线加锁,在整个写操作完全写入主内存后才释放锁! 导致其他的线程都等待,并行处理变成了串行化,效率很低)
为了解决总线加锁效率低下的问题,所以在多处理器下,为了保证多个处理器的缓存是一致的,引入了缓存一致性协议,实现了缓存一致性协议就可以高效的进行数据同步(原因是锁主的是需要修改的变量,锁的粒度大大降低,和concrrenthashmap的分段锁思想有异曲同工之妙)
3.缓存一致性协议(MESI):每个处理器通过嗅探在总线上传播的数据来检测自己缓存的值是不是最新的过期了,发现自己缓存的数据被修改,就会将自己当前的处理器数据设置为无效状态,从而到主内存中读取最新数据刷入cpu的缓存行中
MESI: (修改,独占,共享,无效)
处理器用嗅探技术保证他的内部缓存,使得系统内存和其他处理器数据在总线上保持一致!
4.synchronized原理
以前在程序员的印象中他一直是重量级锁,但是在1.6以后他不是了,jdk官方对他重新进行了大量优化性能大大提高了,我们可以直接使用他加锁是没有问题的
为了优化以前的性能问题,1.6后引入了偏向锁和轻量级锁,以及锁的存储结构和锁的升级
synchronized实现同步基础
1.普通的同步方法,锁是当前对象
2.静态的同步方法,锁是当前class对象
3.对于同步块,锁是当前括号里的对象
有了上面的基础我们来说说synchronized实现原理:
JVM中基于进入和退出使用了monitor监视器对象来实现方法同步和代码块同步,即通过monitorenter和monitorexit指令实现
monitorenter是在编译后插入到同步快的开始位置
monitorexit是插入到方法结束的位置
jvm必须要保证每个monitorenter和monitorexit对应
任何对象都有一个monitor与之关联,当一个monitor被持有后,它处于锁定状态,获取锁都过程就是尝试后去对象头获monitor所有权
5.java对象头解释
synchronized用的锁是存在对象头里的
java对象头里的mark word里默认是对象的hashcode,分代年龄,锁标记位,是否是偏向锁
对象头里的mark word会随着标志位的变化而变化,有如下几种:
1.无锁状态对象头的存储结构是
对象头的hashcode | 对象分代年龄 | 是否是偏向锁 | 锁标志位
2.偏向锁
线程id | epoch | 对象分代年龄 | 是否偏向锁 | 锁标记位
2.gc标记
空 | 锁标记位
3.轻量级锁
指向战中锁记录的指针 | 锁标记位
4.重量级锁
指向互斥(重量级锁)的指针 | 锁标记位
其他类型还有对象类型数据的指针
数据的长度等
下一篇博客准备讲下java锁升级的过程敬请期待:
无锁——》偏向锁——》轻量级锁——》重量级锁 的升级过程详细含义等