java并发原理实现

240 阅读5分钟

java并发底层原理

缓存系统的存储单位:缓存行

可见性问题通过窥探技术 + MESI协议解决

窥探技术:某个处理器缓存通过窥探总线上发生的数据交换,跟踪其他处理器缓存在做什么事,并改变自己对应缓存行的状态,保证数据的一致性(MESI协议)

MESI协议:MESI是缓存行四种状态的首字母缩写,分别是修改 (Modified), 独享 (Exclusive),共享 (Shared),无效 (Invalid)

原子操作的实现

原子操作:不可被中断的一个或者一系列操作

处理器的原子操作实现

处理器:总线锁定,缓存锁定

总线锁定

总线:主要由CPU使用,用来与高速缓存、主存之间传送信息。

总线锁定:使用处理器提供的一个LOCK#信号,当一个处理器在总线上次输出此信号时,其他处理器的请求将被阻塞,那么该处理器可以独占共享内存

缺点:开销大

缓存锁定

在同一时刻,只需保证对某个内存地址的操作是原子性的

缓存锁定:在指令执行期间该缓存行会一直被锁定,其它处理器无法读/写该指令要访问的内存区域,因此能保证指令执行的原子性。

java的原子操作实现

  • 使用CAS实现原子操作
  • 使用锁

CAS

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

public abstract boolean compareAndSet(T obj, int expect, int update);

CAS是利用了处理器提供的lock前缀命令实现的原子性操作

intel手册对lock前缀的说明:

  • 保证对内存对读-改-写操作原子执行(总线锁定或者缓存锁定)
  • 禁止该指令与之前和之后的读和写指令重排序(内存屏障效果)
  • 把写缓存区中的所有数据刷新到内存中

synchronized

synchronized 可以用来修饰以下 3 个层面:

  • 修饰实例方法;(实例锁,锁是当前实例对象)
  • 修饰静态类方法;(对象锁,锁水当前类的class对象)
  • 修饰代码块。(锁是synchronized括号里配置的对象)

被synchronized修饰的方法在被编译为字节码后,在方法的flags属性中会被标记为ACC_SYNCHRONIZED标志。

当虚拟机访问一个被标记为ACC_SYNCHRONIZED 的方法时,会自动在方法的开始和结束(或异常)位置添加monitorentermonitorexit指令。

任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;

monitorenter和monitorexit,可以理解为一把具体的锁。

在这个锁中保存着两个比较重要的属性:计数器和指针。

volatile底层实现

如果声明的volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令:

  • 将这个变量所在缓存行数据写回到系统内存(缓存锁定)
  • 通过缓存一致性机制保证内存的值与各处理器缓存的值的一致性。

java内存模型(JMM)

内存模型:是一套共享内存系统中多线程读写操作行为的规范,解决了 CPU 多级缓存、CPU 优化、指令重排等导致的内存访问问题。

JMM的底层实现原理是内存屏障

内存屏障:是一组处理器指令,JVM会根据不同的操作系统插入不同的指令来禁止特定类型的处理器重排序。

内存屏障有三个作用

  • 阻止屏障两侧的指令重排序;
  • 它会强制将对缓存的修改操作立即写入主存;
  • 如果是写操作,它会导致其他CPU中对应的缓存行无效。

volatile内存语义

volatile特性:可见性,变量读/写原子性。

volatile变量规则:一个volatile域的写---happens-before---volatile域的读

volatile写/读的内存语义

  • 当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。

  • 当读一个 volatile 变量时,JMM 会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

volatile的内存屏障策略:

ReentrantLock的内存语义

实现原理:

以ReentrantLock为例,ReentrantLock的实现依赖于同步框架AbstractQueuedSynchronizer(AQS),AQS使用一个整型的volatile(命名为state)来维护同步状态。

公平锁

  • 公平锁在释放锁的最后写一个volatile变量state
  • 获取锁时首先读这个volatile变量。

根据volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。所以释放锁的线程 happens-before获取锁的线程。

非公平锁

  • 非公平锁的释放和公平锁一样
  • 获取的锁时会用CAS更新volatile变量

final域的内存语义实现

对于final域,编译器和处理器要遵守两个重排序规则:

1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

(先写入final变量,后调用该对象引用)

原因:编译器会在final域的写之后,插入一个StoreStore屏障

2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

(先读对象的引用,后读final变量)

编译器会在读final域操作的前面插入一个LoadLoad屏障