前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >最详细分析Java 内存模型

最详细分析Java 内存模型

作者头像
Tim在路上
发布2020-08-04 21:42:17
2930
发布2020-08-04 21:42:17
举报

并发编程中, 线程之间如何通信及线程之间如何同步, 通信是指线程之间以何种机制来交换 信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。

Java 的并发采用的是共享内存模型,Java 线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。

内存模型的抽象

java中,所有的实例域,静态域和数组元素存储在堆内存中. 局部变量,方法定义参数和异常处理参数定义栈内存中,他们不会有内存可见性问题,不受内存模型影响.

共享内存,只的是共享变量存储在主内存中,但是每一个线程都有一个私有的本地内存. 本地内存中存储了该线程 读/写 共享变量的副本.

javashare.png

A B 两个线程进行通信需要, A 更新本地缓存, A将共享变量刷新到主存中, B 去主存中拉去 A 已经更新后的共享变量.

JMM (Java内存模型) 提供内存可见性保证.

源代码的重排序

执行程序时为提高性能,编译器会对指令做重排序

  1. 编译器优化重排序 2.指令级并行重排序 3. 内存系统重排序

2,3 属于处理器重排序,处理器重排序,JMM会要求Jav编译器在生成指令序列的时候,插入特定类型的内存屏障指令, 通过内存屏障指令来禁止特定类型的处理器重排序.

happens-before

从 JDK5 开始,java 使用新的 JSR -133 内存模型(本文除非特别说明,针对的都 是 JSR- 133 内存模型)。JSR-133 使用 happens-before 的概念来阐述操作之间 的内存可见性。在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那 么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以 是在一个线程之内,也可以是在不同线程之间。

  • 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
  • 监视器锁规则:对一个监视器的解锁,happens- before 于随后对这个监视器 的加锁。
  • volatile 变量规则:对一个 volatile 域的写,happens- before 于任意后续对 这个 volatile 域的读。
  • 传递性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。

volatile

我们把对 volatile 变量的单个读/写,看成是使用同一个锁对这些单 个读/写操作做了同步

代码语言:javascript
复制
class VolatileFeaturesExample {
    
    volatile long vl = 0L;
    //使用 volatile 声明 64 位的 long 型变量
    public void set(long l) {
    vl = l;
    //单个 volatile 变量的写
    }
    public void getAndIncrement () {
    vl++;
    //复合(多个)volatile 变量的读/写
    }
    public long get() {
    return vl;
    //单个 volatile 变量的读
    }
}

假设有多个线程分别调用上面程序的三个方法,实际变为:

代码语言:javascript
复制
class VolatileFeaturesExample {
    long vl = 0L;
    public synchronized void set(long l) {
    // 64 位的 long 型普通变量
    //对单个的普通变量的写用同一个
    锁同步
    vl = l;
    }
    public void getAndIncrement () {
    long temp = get();
    //普通方法调用
    //调用已同步的读方法
    temp += 1L; //普通写操作
    set(temp); //调用已同步的写方法
    }
    public synchronized long get() {
    //对单个的普通变量的读用同一个
    锁同步
    return vl;
    }
}
volatile重排序
  • 当第二个操作是 volatile写时, 不管第一个操作是什么,都不能重排序.
  • 第一个操作是volatile读时,不管第二个操作是什么都不能重排序
  • 第一个操作是volatile写,第二个操作是volatile读时,不能重排序

在每个 volatile 写操作的前面和后面插入一个 StoreStore 屏障。

主要防止: 禁止上面的普通写和下面的volatile 写重排序,防止上面的 volatile 写与下面可能有的 volatile读/写重排序。

代码语言:javascript
复制
class Monitor Example {
    int a = 0;
    public synchronized void writer() {
    a++;
    }
    //1
    //2
    //3
    public synchronized void reader() {
    int i = a;
    //4
    //5
    ......
    }
    //6
}

锁释放时: JMM 会把该线程对应的本地内存中的共享变量刷新到主内存 中。

获取锁时: JMM 会把该线程对应的本地内存置为无效。从而使得被监视器 保护的临界区代码必须要从主内存中去读取共享变量。

代码语言:javascript
复制
class ReentrantLockExample {
    int a = 0;
    ReentrantLock lock = new ReentrantLock();
    public void writer() {
    lock.lock();
    //获取锁
    try {
    a++;
    } finally {
    lock.unlock(); //释放锁
    }
    }
    public void reader () {
    lock.lock();
    //获取锁
    try {
    int i = a;
    ......
    } finally {
    lock.unlock(); //释放锁
    }
    }
}

在 ReentrantLock 中,调用 lock()方法获取锁;调用 unlock()方法释放锁。

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

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

加锁

代码语言:javascript
复制
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread. currentThread ();
    int c = getState();
    //获取锁的开始,首先读 volatile 变量 state
    if (c == 0) {
    if (isFirst(current) &&
    compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(current);
    return true;
    }
    }
    else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0)
    throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
    }
    return false;
}

释放锁

代码语言:javascript
复制
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread. currentThread () != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
    }
    setState(c);
    //释放锁的最后,写volatile变量state
    return free;
}

公平锁在释放锁的最后写 volatile 变量 state;在获取锁时首先读这个 volatile 变 量。 根据 volatile 的 happens-before 规则,释放锁的线程在写 volatile 变量之前可 见的共享变量,在获取锁的线程读取同一个 volatile 变量后将立即变的对获取锁的 线程可见。

核心实现

代码语言:javascript
复制
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

compareAndSet() 方法调用简称为 CAS。JDK 文档对该方法的说明如下: 如果当前状态值等于预期值,则以原子方式将同步 状态设置为给定的更新值。

ReentrantLock 的分析可以看出,锁释放-获取的内存语义的实现至少有下面两种方 式:

  1. 利用 volatile 变量的写-读所具有的内存语义。
  2. 利用 CAS 所附带的 volatile 读和 volatile 写的内存语义。

concurrent 包的实现

Java 线程之间的通信现在有了下面四种方式:

  1. A 线程写 volatile 变量,随后 B 线程读这个 volatile 变量。
  2. A 线程写 volatile 变量,随后 B 线程用 CAS 更新这个 volatile 变量。
  3. A 线程用 CAS 更新一个 volatile 变量,随后 B 线程用 CAS 更新这个 volatile变量。
  4. A 线程用 CAS 更新一个 volatile 变量,随后 B 线程读这个 volatile 变量。

把这些特性整合在一起,就形成了整个 concurrent 包得以实现的基石。

分析 concurrent 包的源代码实现,会发现一个通用化的实现模式:

  1. 首先,声明共享变量为 volatile;
  2. 然后,使用 CAS 的原子条件更新来实现线程之间的同步;
  3. 同时,配合以 volatile 的

AQS,非阻塞数据结构和原子变量类

concur.png

finall 实现

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

  1. 在构造函数内对一个 final 域的写入,与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。
  2. 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操 作之间不能重排序。
代码语言:javascript
复制
public class FinalExample {
    int i; //普通变量
    final int j; //final 变量
    static FinalExample obj;
    public void FinalExample () {
    //构造函数
    i = 1; //写普通域
    j = 2; //写 final 域
    }
    public static void writer () {
    //写线程 A 执行
    obj = new FinalExample ();
    }
    public static void reader () {
    //读线程 B 执行FinalExample object = obj;
    //读对象引用
    int a = object.i; //读普通域
    int b = object.j; //读 final 域
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存模型的抽象
  • 源代码的重排序
    • happens-before
    • volatile
      • volatile重排序
      • concurrent 包的实现
      • finall 实现
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档