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. Redis-设置key过期

    Redis-设置key过期 expire key seconds 设置指定key 多少秒后过期, seconds 为 -1 时表示永不过期 ttl key 查看指定key还有多少秒过期 persist ...

  2. mysql 知识整理

    前言 安装 使用 关于mysql程式的linux命令 启动mysqld 检查端口是否运行 查看数据库程式相关信息 查看mysql版本 查看配置文件位置 登陆mysql 修改密码 SQL命令 查看数据库 ...

  3. fastadmin 金额 字段类型及html验证

    金额 字段类型  整数  小数 decimal    10      2 float          10     2 html验证 <div class="form-group&q ...

  4. 由函数$y=\sin x$的图像伸缩变换为函数$y=\sin(\omega x)$的图像(交互式)

    可以拖动滑动条\(\omega\)显示动态效果

  5. 一例jsonp跨域访问

    对于网站A,有一链接 '/auth/list',返回json数据 {, , , , , },{, , , , , }]} 网站b某页面下可以这样写jsonp get请求 <script> ...

  6. python中的list,tuple,dict,set简介---陈雨童

    变量和对象 变量把对象和自己连接起来(指针连接对象空间),引用建立了变量和对象之间的映射关系,这就是引用.引用完成,就实现了赋值.变量通过对象的内存地址指向对象,类似于软链接 将变量a赋值给变量b,其 ...

  7. html 与 jsp 文件格式区别

    html 与 jsp 文件格式区别 html5 <!DOCTYPE html> <html> <head> <meta charset="UTF-8 ...

  8. 题解 [CQOI2015]任务查询系统

    题面 解析 首先,我们考虑下暴力的做法: 每次将一个任务的重要度加入到它的区间中, 询问的时候就直接加前\(k\)大. 然而,这样肯会炸的(都说了是暴力了). 其实,我们可以转化一下区间修改(因为区间 ...

  9. Python字符串运算符

    下表实例变量 a 值为字符串 "Hello",b 变量值为 "Python": 操作符 描述 实例 + 字符串连接 >>>a + b 'Hel ...

  10. 一、Django入门

    1.安装: 下载地址:https://www.djangoproject.com/download/ 原文节选: How to get Django Django is available open- ...