java并发编程3——volatile和synchronized的底层

java并发编程3——volatile和synchronized的底层

java代码编译成字节码,然后被类加载器加载到jvm中,jvm执行,最终转换为汇编指令在cpu上执行,java的并发机制其实依赖的是jvm和cpu的指令。

volatile

volatile加在共享变量上,保证所有线程看到这个变量的值是一致的,即valoatile变量相当于加了一个读写锁,是通过原子操作实现的。

实现原理:汇编代码给volatile变量加了lock前缀指令。

lock指令:将当前cpu缓存的volatile变量立刻写回系统内存,并且使其他cpu缓存中的volatile变量数据无效。(是通过硬件的EMSI实现,不懂的朋友可以参考上篇EMSI的文章)

synchronized

synchronized保证一个方法或者代码块同时只有一个线程执行(不会发生中断和抢占)。

Java中每个对象都可以作为锁(对象头)
具体实现:

  1. 普通同步方法:锁是当前实例对象。

  2. 静态同步方法:锁是当前类的class文件。

  3. 对于配置了synchonized对象的,锁是配置对象。

当进程访问代码块时,必须先得到锁,退出或抛出异常时释放锁。

synchronized是通过jvm实现的,jvm是通过monitorenter和monitorexit指令实现的。

monitorenter获得对象锁,monitorexit释放对象锁。对象锁存在于对象头中。

对象头的内容

markword:存储对象的hashcode和锁信息。
ClassMetadataAddress:对象的元数据(包括方法表,构造方法,成员变量。。)
arrayLength:数组的长度(如果是数组的话,如果不是数组没有这个)

锁存在于MarkWord中:

锁状态 25bit 4bit 1bit是否是偏向锁 2bit锁标志位
对象的hashcode 对象分代年龄 0 01

各种锁的MarkWord:

轻量级锁 指向 栈中锁记录的 指针 00
重量级锁 指向互斥 量(重量级锁) 的指针 10
GC标记 11
偏向锁 线程ID epoch,对象分代年龄 1 01

(2)锁升级

在synchronized中有四种级别的锁:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。

锁只能升级,不能降级。

(1)偏向锁:一个锁由一个对象多次获得,为了代价最低,引入了偏向锁

当一个线程获得了无锁状态的锁,那么就会将自己线程ID通过CAS记录进对象头MarkWord中,从无锁升级为偏向锁。

当线程再次获得了锁对象,就检测一下锁对象的对象头的MardWCASDord中的偏向锁是否是自己的线程ID,如果是直接用,如果不是则用CAS修改为自己的线程ID,然后使用。

如果CAS一次成功则获得偏向锁,如果失败则进行锁升级。

(2)轻量级锁(不会阻塞线程,线程自适应自旋拿锁):

jvm现在当前线程的栈中创建用于存储锁记录的空间,并且将对象头中的MarkWord复制到锁记录中,然后线程会尝试用CAS将对象头中的markword替换为指向锁的指针(指向栈中锁的指针),如果成功,当前线程获得锁,如果失败,表示其他进程在竞争锁,当前线程便使用自旋来获得锁。

如果自旋很多次都没有获得锁,那么就会膨胀成重量级锁。

(3)重量级锁(阻塞线程)

这是一个操作系统的互斥锁(或者是信号量),一旦线程完成了synchronized就会释放锁,其他进程竞争锁。
开销比较大,应对的是高竞争。
没有获得锁的进程会进入阻塞,当有锁时候会被唤醒,这会造成上下文切换,造成较大的性能损失。

处理器实现原子操作

当存在跨总线宽度,跨多个缓存行和跨页表时候,处理器不能保证其原子性,但是可以提供总线锁定和缓存锁定来保证原子性。

MESI协议
(1)总线锁定:当一个cpu处理时,其他cpu无法访问这个内存。
(2)缓存锁定:使用缓存一致性MESI,使用缓存行时候,其他cpu缓存中的共享缓存行设置为invalied。

你可能感兴趣的:(并发编程,java,开发语言)