JVM学习笔记——内存模型篇( 二 )

原子性实现那么我们该如何实现多线程的原子性:

  • 使用synchronized(同步关键字)
我们这里给出synchronized的使用方式:
synchronized( 对象 ) { // 要作为原子操作代码}我们如果要实现之前的代码 , 我们可以将代码修改为:
package cn.itcast.jvm.t4.avo;public class Demo4_1 {// 这里的i应该被多线程共用 , 设为静态变量static int i = 0;// 这里是Obj对象 , 我们设置它为锁 , 注意两个线程中的synchronized所对应的锁应该是同一个对象(锁)static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 采用synchronized设置锁实现原子性 , 这样i++操作就会完整进行Thread t1 = new Thread(() -> {synchronized (obj) {for (int j = 0; j < 50000; j++) {i++;}}});Thread t2 = new Thread(() -> {// 采用synchronized设置锁实现原子性 , 这样i--操作就会完整进行synchronized (obj) {for (int j = 0; j < 50000; j++) {i--;}}});t1.start();t2.start();t1.join();t2.join();// 我们的输出结果自然是0了~System.out.println(i);}}内存模型之可见性我们将在下面仔细介绍可见性的特点
可见性介绍首先我们简单介绍一下可见性的定义:
  • 我们需要保证 , 在多个线程中 , 对同一变量的修改需要被其他线程所知道并且可以调用
可见性的注意点:
  • 我们的程序往往具有自动优化 , 对于多次取同一值的数据可能会封装在自己的程序中而不是在源程序读取 , 这就会导致可见性失效
可见性问题我们同样给出一段代码作为可见性的案例:
package cn.itcast.jvm.t4.avo;public class Demo4_2 {static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while(run){}});t.start();Thread.sleep(1000);run = false; // 线程t不会如预想的停下来}}我们的运行结果如下:
// 我们上述代码希望:程序在执行1s后停止运行 , 但我们的程序却一直运行不会停止...可见性分析首先我们回顾开头的注意点:
  • 程序具有自身很多的优化步骤 , 可能哪一步就会导致我们的程序出错
我们来简单分析:
  1. 初始状态 ,  t 线程刚开始从主内存读取了 run 的值到工作内存 。

JVM学习笔记——内存模型篇

文章插图
  1. 线程要频繁从主内存中读取 run 的值 , JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中 , 减少对主存中 run 的访问

JVM学习笔记——内存模型篇

文章插图
  1. 1 秒之后 , main 线程修改了 run 的值 , 并同步至主存 , 而 t 是从自己工作内存中的高速缓存中读取这个变量的值 , 结果永远是旧值

JVM学习笔记——内存模型篇

文章插图
可见性实现我们的可见性经常通过一种修饰词来实现:
  • volatile(易变关键字)
  • 它可以用来修饰成员变量和静态成员变量
  • 他可以避免线程从自己的工作缓存中查找变量的值 , 必须到主存中获取它的值 , 线程操作 volatile 变量都是直接操作主存
同时我们给出另一种方法: