Java 内存模型(Java Memory Model,JMM)看上去和 Java 内存结构(JVM 运行时内存结构)差不多,但这两者并不是一回事。JMM 并不像 JVM 内存结构一样是真实存在的,它只是一个抽象的概念。
Java 的线程间通过共享内存(Java堆和方法区)进行通信,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而 JMM 就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。
现在说 JMM 一般指的是 JDK 5 开始使用的新的内存模型,主要由 JSR-133: JavaTM Memory Model and Thread Specification(http://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(); }
有序性
程序执行的顺序按照代码的先后顺序执行。
编译器和处理器会对指令进行重排序来达到优化效果,重排序后不会影响单线程执行的结果,但可能会影响多线程并发执行的结果。
二、Java 内存模型
为了保证共享内存的正确性(可见性、有序性、原子性),JMM 定义了共享内存系统中多线程程序读写操作行为的规范。
JMM 解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。
主内存与工作内存
Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,用于存储主存中要使用的变量的副本。
线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
不同的线程之间无法直接访问对方工作内存中的变量。
线程间变量的传递需要自己的工作内存和主存之间进行数据同步。
主内存与工作内存间交互操作(了解)
Java 内存模型定义了八种操作来完成
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。 unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。 write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
happens-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见(并不意味着前一个操作必须要在后一个操作之前执行),那么这两个操作之间必须要存在 happens-before 关系。
三、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/
原文地址:https://www.cnblogs.com/jhxxb/p/10940149.html