Java 内存模型(Java Memory Model,JMM)看上去和 Java 内存结构(JVM 运行时内存结构)差不多,但这两者并不是一回事。JMM 并不像 JVM 内存结构一样是真实存在的,它只是一个抽象的概念。

Java 的线程间通过共享内存(Java堆和方法区)进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而 JMM 就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。

现在说 JMM 一般指的是 JDK 5 开始使用的新的内存模型,主要由 JSR-133: JavaTM Memory Model and Thread Specificationhttp://www.cs.umd.edu/users/pugh/java/memoryModel/http://ifeve.com/jsr133-cn/)描述。

一、多线程的特性

原子性

一个操作不可被中断,要么执行完成,要么就不执行。

public static int k = 0;
// 多线程去操作一个共享变量,多运行几次,可以看到不一样的结果
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
tpe.execute(() -> {
k++;
});
}
Thread.sleep(1000);
System.out.println(k);
tpe.shutdown();
}

可见性

多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。

private static boolean flag = false;
// 多运行几次,会出现无法结束的情况
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("------------------");
break;
}
}
}).start(); // Thread.sleep(100); new Thread(() -> {
flag = true;
}).start();
}

有序性

程序执行的顺序按照代码的先后顺序执行。

编译器和处理器会对指令进行重排序来达到优化效果,重排序后不会影响单线程执行的结果(as-if-serial),但可能会影响多线程并发执行的结果。

二、Java 内存模型

为了保证共享内存的正确性(可见性、有序性、原子性),JMM 定义了共享内存系统中多线程程序读写操作行为的规范。

JMM 解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。

主内存与工作内存

Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,用于存储主存中要使用的变量的副本。

线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。

不同的线程之间无法直接访问对方工作内存中的变量。

线程间变量的传递需要自己的工作内存和主存之间进行数据同步。

主内存与工作内存间交互操作(了解)

Java 内存模型定义了八种操作来完成。

在 32 位计算机上,对于 long double 64位读取,需要进行两次处理,非原子性操作。在 64 位上无问题。

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

happens-before(了解)

在JMM中,如果一个操作执行的结果需要对另一个操作可见(并不意味着前一个操作必须要在后一个操作之前执行),那么这两个操作之间必须存在 happens-before 关系。

happens-before 的八大原则:

1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;
3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5.线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;
6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive0的返回值手段检测到线程已经终止执行;
8.对象终结规则:一个对象的初始化完成先行发生于他的finalize)方法的开始;

如果两个操作不满足上述任意一个 happens-before 规则,那么这两个操作就没有顺序的保障,JVM可以对这两个操作进行重排序。如果操作A happens-before 操作 B,那么操作 A 在内存上所做的操作对操作 B 都是可见的。

三、Java 内存模型的实现

Java 内存模型,除了定义了一套规范,还提供了一系列语义,封装了底层实现后,供开发者直接使用。

如 volatile、synchronized、final、concurren 包等。这些就是 Java 内存模型封装了底层的实现后提供给程序员使用的一些关键字。

原子性

给关键代码加锁

public static int k = 0;

public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
tpe.execute(() -> {
synchronized (tpe) {
k++;
}
});
}
Thread.sleep(1000);
System.out.println(k);
tpe.shutdown();
}

用 concurren 包中的原子变量代替基本变量

public static AtomicInteger k = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
tpe.execute(() -> {
k.incrementAndGet();
});
}
Thread.sleep(1000);
System.out.println(k);
tpe.shutdown();
}

可见性

给变量加上 volatile 修饰

private static volatile boolean flag = false;

public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("------------------");
break;
}
}
}).start(); Thread.sleep(100); new Thread(() -> {
flag = true;
}).start();
}

有序性

使用 synchronized 和 volatile 来保证多线程之间操作的有序性。

volatile 关键字会禁止指令重排。synchronized 关键字保证同一时刻只允许一条线程操作。


https://www.hollischuang.com/archives/2509

https://www.hollischuang.com/archives/2550

http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/

Java-内存模型(JSR-133)的更多相关文章

  1. JMM(java内存模型)

    What is a memory model, anyway? In multiprocessorsystems, processors generally have one or more laye ...

  2. Java内存模型深度解析:基础部分--转

    原文地址:http://www.codeceo.com/article/java-memory-1.html 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何 ...

  3. java 内存模型

    翻译自wiki百科:https://en.wikipedia.org/wiki/Java_memory_model 没找到直接在wiki上编辑中文的页面,我就在这翻译下,自己学习用. java内存模型 ...

  4. java内存模型-顺序一致性

    数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.java 内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量, 在另一个线程读同一个变量, 而且写和读没有通过同步来排序. 当代 ...

  5. java内存模型-基础

    基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间 ...

  6. 【Todo】【转载】深入理解Java内存模型

    提纲挈领地说一下Java内存模型: 什么是Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范.Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几 ...

  7. 深入理解Java内存模型(一)——基础(转)

    转自程晓明的"深入理解Java内存模型"的博客 http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发 ...

  8. 修复 Java 内存模型,第 2 部分——Brian Goetz

    转自Java并发大师Brain Goetz:http://www.ibm.com/developerworks/cn/java/j-jtp03304/ (中文地址) http://www.ibm.co ...

  9. 修复 Java 内存模型,第 1 部分——Brian Goetz

    转自Java并发大师Brain Goetz:http://www.ibm.com/developerworks/cn/java/j-jtp02244/ (中文地址) http://www.ibm.co ...

  10. 深入理解Java内存模型(一)——基础

    并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信 ...

随机推荐

  1. Ajax与JSON,XML,PHP

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 公司最喜欢问的Java集合类

    java.util包中包含了一系列重要的集合类,而对于集合类,主要需要掌握的就是它的内部结构,以及遍历集合的迭代模式. 接口:Collection Collection是最基本的集合接口,一个Coll ...

  3. shell脚本中的EOF以及文件重定向

    <<EOF  (内容)  EOF  可以把EOF替换成其他东西(分解符)  意思是把内容当作标准输入传给程序 这里再简要回顾一下<<的用法.当Shell看到<<的时 ...

  4. 第十一章· MHA高可用及读写分离

    一.MHA简介 1.1.作者简介 松信嘉範: MySQL/Linux专家 2001年索尼公司入职 2001年开始使用oracle 2004年开始使用MySQL 2006年9月-2010年8月MySQL ...

  5. 【ARC072 E】Alice in linear land

    被智商题劝退,告辞 题意 有一个人在一条数轴的距离原点为 \(D\) 的位置,他可以执行 \(n\) 次操作,每次操作为给定一个整数 \(d_i\),这个人向原点的方向走 \(d_i\) 个单位,但如 ...

  6. 《Python基础教程》第二章:列表和元组(2)

    list函数可以将字符串转换为列表 ' '.join(somelist)可以将列表转换为字符串 从列表中删除元素可以使用del语句来实现 方法是与对象有紧密联系的函数:对象.方法(参数) append ...

  7. Nginx概念

    这篇文章只是单纯的介绍nginx以及一些相关概念,有的概念在实际应用中不会用到,不理解也没有关系,这不影响我们学习nginx. Nginx是什么 如果你知道http协议和httpd是什么,那你就会容易 ...

  8. hive的事物性 transaction manager

    create table lk3 (id string,nname string,grade int,goldUser int); insert into lk3 values (,, ), (,, ...

  9. What is a Digital Signature? 理解数字签名与数字证书

    What is a Digital Signature?An introduction to Digital Signatures, by David Youd Bob (Bob's public k ...

  10. JavaScript 中 console 的用法 -------------------引用

    1.在调试 JS 代码时,很多人倾向于使用 alert() 或者 console.log() 方法来输出信息,正如某些 Java 程序员喜欢在调试代码时使用System.out.println() 输 ...