H5W3
当前位置:H5W3 > java > 正文

【Java】Java多线程之Java内存模型

Java多线程之Java内存模型

WillLiaowh发布于 今天 02:23

为什么要有Java内存模型

Java是运行在Java虚拟机的,相当于在操作系统之上建立的一个虚拟的计算机,Java虚拟机想要做到跨平台,就需要定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,通过内存模型来保证了并发场景下的一致性、原子性和有序性。

Java内存模型的具体内容

主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,而不能直接读写内存中的变量,不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
【Java】Java多线程之Java内存模型
如图,如果线程想要通信的话要执行一下步骤:
A线程先把工作内存的值写入主内存
B线程从主内存中去读取出A线程写的值

内存间交互操作
  1. lock,锁定,所用于主内存变量,它把一个变量标识为一条线程独占的状态。
  2. unlock,解锁,解锁后的变量才能被其他线程锁定。
  3. read,读取,所用于主内存变量,它把一个主内存变量的值,读取到工作内存中。
  4. load,载入,所用于工作内存变量,它把read读取的值,放到工作内存的变量副本中。
  5. use,使用,作用于工作内存变量,它把工作内存变量的值传递给执行引擎,当JVM遇到一个变量读取指令就会执行这个操作。
  6. assign,赋值,作用于工作内存变量,它把一个从执行引擎接收到的值赋值给工作内存变量。
  7. store,存储,作用域工作内存变量,它把工作内存变量值传送到主内存中。
  8. write,写入,作用于主内存变量,它把store从工作内存中得到的变量值写入到主内存变量中

原子性可见性有序性

Java内存模型的相关操作和规则,是围绕着并发过程中如何处理原子性,可见性,有序性3个特征来建立的。

原子性
    public class TestAtomicity {
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
Runnable increment = () -> {
for (int i = 0; i < 1000; i++) {
number++;
}
};
ArrayList<Thread> ts = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(increment);
t.start();
ts.add(t);
}
for (Thread t : ts) {
t.join();
}
System.out.println("number = " + number);
}
}

使用javap反汇编class文件,得到下面的字节码指令:

9: getstatic #12 // Field number:I
12: iconst_1
13: iadd
14: putstatic #12 // Field number:I

由此可见number++是由多条语句组成,以上多条指令在一个线程的情况下是不会出问题的,但是在多线程情况下就可能会出现问题。比如一个线程在执行13: iadd时,另一个线程又执行9: getstatic。会导致两次number++,实际上只加了1
结论:
并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。

可见性
    public class TestVisibility {
private static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (run) {
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(() -> {
run = false;
System.out.println("时间到,线程2设置为false");
});
t2.start();
}
}

一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另 一个线程并不会停止循环。
结论:
并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值

有序性

程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。

javajmm
阅读 35更新于 今天 02:55
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
avatar

WillLiaowh

世界上最伟大的力量是坚持。

19 声望
5 粉丝

0 条评论
得票时间

avatar

WillLiaowh

世界上最伟大的力量是坚持。

19 声望
5 粉丝

宣传栏

为什么要有Java内存模型

Java是运行在Java虚拟机的,相当于在操作系统之上建立的一个虚拟的计算机,Java虚拟机想要做到跨平台,就需要定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,通过内存模型来保证了并发场景下的一致性、原子性和有序性。

Java内存模型的具体内容

主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,而不能直接读写内存中的变量,不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
【Java】Java多线程之Java内存模型
如图,如果线程想要通信的话要执行一下步骤:
A线程先把工作内存的值写入主内存
B线程从主内存中去读取出A线程写的值

内存间交互操作
  1. lock,锁定,所用于主内存变量,它把一个变量标识为一条线程独占的状态。
  2. unlock,解锁,解锁后的变量才能被其他线程锁定。
  3. read,读取,所用于主内存变量,它把一个主内存变量的值,读取到工作内存中。
  4. load,载入,所用于工作内存变量,它把read读取的值,放到工作内存的变量副本中。
  5. use,使用,作用于工作内存变量,它把工作内存变量的值传递给执行引擎,当JVM遇到一个变量读取指令就会执行这个操作。
  6. assign,赋值,作用于工作内存变量,它把一个从执行引擎接收到的值赋值给工作内存变量。
  7. store,存储,作用域工作内存变量,它把工作内存变量值传送到主内存中。
  8. write,写入,作用于主内存变量,它把store从工作内存中得到的变量值写入到主内存变量中

原子性可见性有序性

Java内存模型的相关操作和规则,是围绕着并发过程中如何处理原子性,可见性,有序性3个特征来建立的。

原子性
    public class TestAtomicity {
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
Runnable increment = () -> {
for (int i = 0; i < 1000; i++) {
number++;
}
};
ArrayList<Thread> ts = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Thread t = new Thread(increment);
t.start();
ts.add(t);
}
for (Thread t : ts) {
t.join();
}
System.out.println("number = " + number);
}
}

使用javap反汇编class文件,得到下面的字节码指令:

9: getstatic #12 // Field number:I
12: iconst_1
13: iadd
14: putstatic #12 // Field number:I

由此可见number++是由多条语句组成,以上多条指令在一个线程的情况下是不会出问题的,但是在多线程情况下就可能会出现问题。比如一个线程在执行13: iadd时,另一个线程又执行9: getstatic。会导致两次number++,实际上只加了1
结论:
并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。

可见性
    public class TestVisibility {
private static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (run) {
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(() -> {
run = false;
System.out.println("时间到,线程2设置为false");
});
t2.start();
}
}

一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另 一个线程并不会停止循环。
结论:
并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值

有序性

程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。

本文地址:H5W3 » 【Java】Java多线程之Java内存模型

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址