java并发底层原理及JMM

整理自《java并发编程的艺术》

一,java并发机制底层实现原理

1.volatile “易变的”

volatile 是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个县城修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

1.1 volatile的定义与实现原理

1)Lock前缀指令会引起处理器缓存回写到内存。为了保证原子性,一般锁定缓存,但也会锁定数据总线。原则一通俗讲就是讲工作缓存中的数据刷新到主内存中。

2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。原则二就是保证volatile的可见性,保证volatile修饰的内容对所有线程都是可见的,当一条线程在修改数据时,其他所有线程的工作内存里该内容的副本置为无效,当线程对其修改完成后,所有相关线程从主内存读取该内容,刷新到各自的工作内存中。

2. synchronized的实现原理与应用
2.1 synchronized的表现形式:
 ·对于普通同步方法,锁就是当前实例对象。
 ·对于静态同步方法,锁就是当前类的Class对象。
 ·对于同步方法块,锁是synchronized括号中配置的对象。

在JVM规范中synchronized的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。

2.2 java对象头

synchronized用的锁存在java对象头中。java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。

2.3 锁的升级与对比

锁一共有4中状态:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。这几个状态随着竞争逐渐升级,一旦升级,就不能降级。

1)偏向锁
同一个线程多次获得锁,为了降低线程获得锁的代价引入偏向锁。

。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,
只需简单地测试一下对象头的MarkWord里是否存储着指向当前线程的偏向锁。

如果测试成功,表示线程已经获得了锁。

如果测试失败,则需要再测试一下MarkWord中偏向锁的标识是否设置成1(表示当前是偏向锁):

如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

2)轻量级锁
线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为DisplacedMarkWord。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

3)重量级锁
image

3. 原子操作实现原理

处理器实现原理操作:

1)使用总线锁保证原子性
2)使用缓存锁保证原子性

java实现原子操作

java中通过锁和循环CAS的方式实现原子操作。
(CAS Compare and Swap , CAS操作需要输入两个数值,一个旧值和一个新值,在操作期间先比较旧值有没有发生变化,如果没有,交换新值;如果有,不交换。)  

二,JMM (java 内存模型)

1. java内存模型基础
1.1 并发编程模型两个关键问题:
    ·线程如何通信  
    ·线程之间如何同步。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写读内存中的公共状态进行隐式通信。  

在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
1.2 JMM抽象结构

image
1)线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2)线程B到主内存中去读取线程A之前已更新过的共享变量。

1.3 重排序
重排序分3种类型:
1)编译器优化的重排序。

2)指令级并行的重排序。

3)内存系统的重排序。

image

JMM属于语言级的内存模型,他确保在不同的编译器和不同的处理平台上,通过禁止特定类型的编译器重排序和处理器重排序,位程序员提供一致的内存可见性保证。

1.4 并发模型分类

image

为了保证内存可见性,java编译器在生成指令序列的适当位置插入内存屏障指令来禁止特定类型的处理器重排序。

image

1.5 happen-before

从JDK5开始,Java使用新的JSR133内存模型(除非特别说明,本文针对的都是JSR133内存模型)。JSR133使用happensbefore的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happensbefore关系。

2. 重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

2.1数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

2.2 as-if-serial

asifserial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守asifserial语义。

asifserial语义把单线程程序保护了起来,遵守asifserial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。asifserial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

2.3重排序对多线程的影响

1,对于没有数据以来关系的操作,可重排序。
2,对于存在控制依赖关系(if)的两个操作,编译器和处理器会采用猜测执行来克服控制相关性和并行度的影响。提前读取,当满足控制条件时复制给对应操作。

3. 顺序一致性

顺序一致性内存模型有两大特性。
1)一个线程中的所有操作必须按照程序的顺序来执行。
2)(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
image

image

4.volatile
4.1特性

·可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
·原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

4.2 volatile内存语义实现
volatile重排序规则
image

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。

·在每个volatile写操作的前面插入一个StoreStore屏障。
·在每个volatile写操作的后面插入一个StoreLoad屏障。
·在每个volatile读操作的后面插入一个LoadLoad屏障。
·在每个volatile读操作的后面插入一个LoadStore屏障。
5.锁的内存语义

锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。

ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(本文简称之为AQS)。AQS使用一个整型的volatile变量(命名为state)来维护同步状态,这个volatile变量是ReentrantLock内存语义实现的关键。

ReentrantLock分为公平锁和非公平锁,我们首先分析公平锁。

·公平锁和非公平锁释放时,最后都要写一个volatile变量state。

·公平锁获取时,首先会去读volatile变量。

·非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义。

6.final域的内存语义

对于final域,编译器和处理器遵循两个重排序规则。
1)在构造函数内对一个final域的写入,与随后初次读这个final域,这两个操作之间不能重排序。
2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含下面2个方面。

1)JMM禁止编译器把final域的写重排序到构造函数之外。

2)编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。

7.happens-before

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值