并发机制的底层实现原理

并发机制的底层实现原理

volatile

在多线程并发编程中synchronized和volatile关键字都扮演着重要角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外的线程可以读到修改后的值。volatile的恰当使用能比synchronized关键字的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
在X86处理器下通过工具获取JIT编译器生成的汇编指令,查看对volatile变量进行写操作时,对应CPU的操作,例子如下:

  • Java代码如下:

    1
    instance = new Singleton();    //instance是volatile变量
  • 转化为汇编代码,如下:

    1
    0x01a3de1d: movb $0x0,ox1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

有volatile变量修饰的共享变量进行写操作时会多出两行汇编代码,通过查看IA-32架构软件开发者手册可知,Lock前缀的指令在多核处理器中会发生以下两件事情。

  1. 将当前处理器的缓存行的数据写回到系统内存;
  2. 这个写回内存的操作会是其他CPU里缓存了该内存地址的数据无效。

synchronized

在多线程并发编程中synchronized一直是元老级角色,很多人称呼它为重量级锁。Java SE1.6对synchronized进行了各种优化,为减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为一些3种形式:

  • 对于普通同步方法,锁是当前实例对象;
  • 对于静态同步方法,锁是当前类的Class对象;
  • 对于同步代码块,锁是Synchronized括号里配置的对象。

当一个线程师徒访问同步代码块时,它首先必须得到锁,退出或者抛出异常时必须释放锁。
从JVM规范中可以看到Synchronized在JVM里实现的原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者实现细节不同。代码块同步用monitorenter和monitorexit指令实现,而方法同步是使用另一种方式实现的,细节没有在JVM规范中提及。但方法的同步同样可以使用上述两个指令实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有之后,它将处于被锁状态。线程执行到monitor指令时,将尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

锁的升级

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁,在Java SE 1.6中,锁一共有4个状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态随着竞争情况逐渐升级。锁可以升级但不可以降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。这种锁只能升级不能降级的策略是为了提高获得锁和释放锁的效率。

原子操作

原子操作(atomic operation)指的是不可被中断的一个或者一系列操作。
处理器保证从系统内存中读取或写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。但是负责的内存操作处理器是不能自动保证原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定缓存锁定两个机制来保证复杂内存操作的原子性。

  • 总线锁是指使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器就可以独占共享内存。但是总线锁定把CPU和内存之间的通信锁定了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定开销比较大。
  • 缓存锁定是指内存区域如果被缓存在处理器的缓存行中,并且在LOCK操作期间被锁定,那么当它执行写操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效

CAS实现原子操作的三大问题:

  1. ABA问题;
  2. 循环时间长开销大;
  3. 只能保证一个共享变量的原子操作。
-------------本文结束感谢您的阅读-------------