Java-内存模型(JSR-133)

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

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

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

 

1、多线程的特性

原子性

一个操做不可被中断,要么执行完成,要么就不执行。多线程

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(); }

有序性

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

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

 

2、Java 内存模型

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

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

 

主内存与工做内存

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 都是可见的。

 

3、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(); }
View Code

用 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(); }
View Code

可见性

给变量加上 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(); }
View Code

有序性

使用 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/

相关文章
相关标签/搜索