关于java:蹲坑也能进大厂多线程系列Java内存模型精讲

38次阅读

共计 4018 个字符,预计需要花费 11 分钟才能阅读完成。

前言

后面两期咱们介绍了多线程的根底知识点,都是一些面试高频问题,没有看和遗记的小伙伴能够回顾一下。

《蹲坑也能进大厂》多线程这几道根底面试题,80% 小伙伴第一题就答错

《蹲坑也能进大厂》多线程系列 - 上下文、死锁、高频面试题

本章次要是剖析一下大家十分 面生 的 Java 内存模型,用代码的形式介绍重排序、可见性以及线程之间通信等原理,大家看完本篇必然有更加分明的意识和了解。

狗剩子:花 GieGie~, 节日快乐啊!这么早就来蹲坑。

我:哟,狗剩子你明天又来加班了,365 天无休啊你。

狗剩子:这不明天过节,没有什么好货色送给各位看官,只能肝进去一些干货送给老铁们么。

我:接招吧,狗儿。

注释

我:书接上文,狗剩子你给大伙讲讲什么是 volatile?

上来就搞这么刺激的吗,你让咱家想想 …

我:ok,小辣鸡,那我换个问题,你理解过 Java 内存模型吗?

这个不是三伏天喝冰水,正中下怀么。

Java 内存模型(Java Memory Model)简称 JMM,首先要晓得它是一组标准,是一组 多线程拜访 Java 内存 的标准。

咱们都晓得市面上 Java 虚拟机品种有很多,比方 HotSpot VM、J9 VM 以及各种实现(Oracle / Sun JDK、OpenJDK),而每一种虚拟机在解释 Java 代码、并进行重排序时都有本人的一套流程,如果没有 JMM 标准,那很有可能雷同代码在不同 JVM 解释后,失去的运行后果也是不统一的,这是咱们不心愿看到的。

我:有点意思,但这种说法还是有点含糊,你再具体说说它都有哪些标准?

厌恶,就晓得你会这么问,小伙们提到 Java 内存模型咱们第一工夫要想到 3 个局部,重排序 可见性 原子性

  • 重排序

    先看一段代码,给你几分钟工夫,看看这段代码输入有几种后果

    private static int x = 0, y = 0;
    private static int a = 0, b = 0;
    
    Thread one = new Thread(new Runnable() {
        @Override
        public void run() {
            a = 1;
            x = b;
        }
    });
    Thread two = new Thread(new Runnable() {
        @Override
        public void run() {
            b = 1;
            y = a;
        }
    });
    two.start();
    one.start();
    one.join();
    two.join();
    System.out.println("x ="+x+", y ="+y);

你的答案是不是这三种呢

如果是的话,那么祝贺你,能够持续和狗哥我一块持续往下钻研 第四种状况

这里我减少了一个 for 循环,能够循环打印,直到打印本人想要的后果,小伙伴们本人运行一下。

private static int x = 0, y = 0;
private static int a = 0, b = 0;

public static void main(String[] args) throws InterruptedException {
    int i = 0;
    for (; ;) {
        i++;
        x = 0;
        y = 0;
        a = 0;
        b = 0;

        CountDownLatch latch = new CountDownLatch(3);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {latch.countDown();
                    latch.await();} catch (InterruptedException e) {e.printStackTrace();
                }
                a = 1;
                x = b;
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {latch.countDown();
                    latch.await();} catch (InterruptedException e) {e.printStackTrace();
                }
                b = 1;
                y = a;
            }
        });
        thread2.start();
        thread1.start();
        latch.countDown();
        thread1.join();
        thread2.join();

        String result = "第" + i + "次(" + x + "," + y + ")";
        if (x == 0 && y == 0) {System.out.println(result);
            break;
        } else {System.out.println(result);
        }
    }
}

看看你执行到多少次会呈现呢,这里我是执行到将近 17 万次。

为什么会呈现这种状况呢,那是因为这里产生了 重排序,在重排序后,代码的执行程序变成了:

  • y=2;
  • a=1;
  • x=b;
  • b=1;

这里就能够总结一下 重排序 ,艰深的说就是代码的执行程序和代码在文件中的 程序不统一 ,代码指令并没有严格依照代码语句程序执行,而是依据本人的规定进行调整了,这就是 重排序

我:这个例子有点货色,简单明了,我都看懂了?那可见性又怎么了解呢

既然例子比拟直观,那这个问题我持续用例子来解释一波。

  • 可见性
public class Visibility {
   int a = 1;
   int b = 2;

   private void change() {
      a = 3;
      b = a;
   }


   private void print() {System.out.println("b=" + b + ";a=" + a);
   }

   public static void main(String[] args) {while (true) {Visibility visibility = new Visibility();
         // 线程 1
         new Thread(() -> {
            try {Thread.sleep(1);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            visibility.change();}).start();
        // 线程 2
         new Thread(() -> {
            try {Thread.sleep(1);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            visibility.print();}).start();}
   }
}

这里同样倡议停留几分钟,你感觉 print()打印后果有几种呢,多思考能力了解更粗浅。

  • a=1,b=2 : 线程 1 未执行到change(),此时线程 2 已执行print()
  • a=3,b=2: 线程 1 执行到 change() 的 a = 3,而后线程 2 正好执行print()
  • a=3,b=3:线程 1 执行完change(),而后线程 2 执行print()

这是大家最容易想到和了解的 ( 如果没有想到,记得去补习一下花 Gie 的前两篇根底),然而还有一种状况比拟非凡:

  • b=3,a=1

是不是没想到啊 (手动得意),这里咱们如果线程 1 执行完change() 办法后,此时 a= 3 且 b =3,然而这时只是 线程 1 本人晓得这个后果值,对于 线程 2 来说,他可能只看到了一部分,呈现这种状况的起因,是因为线程之间通信是有延时的,而且多个线程之间不会进行实时同步,所以 线程 2 只看到了 b 的最新值,并没有看到 a 的扭转。

我:你这么说的话,我如同有点明确了,但还不是很清晰

你能够再说说这个变量是怎么传递的吗,为什么线程 2 没有接管到 a 的变动呢?

好的呢,我都依你,我间接上个简略的草图吧。

<image width=300 src=”https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69b6f4691db84a31805c0ee0016a45d9~tplv-k3u1fbpfcp-watermark.image”></image>

<image width=300 src=”https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c254ade514ce44c583b66ecf36e8c4c6~tplv-k3u1fbpfcp-watermark.image”></image>

图中咱们剖析出以下 4 个步骤。

  • 每个线程都会从主内存中获取变量,保留在本人的工作内存(线程公有)中,图 1 是 线程 1 线程 2 初始化状态;
  • 图 2 是线程 1 执行完 change() 办法后,先将 b=3 写回主内存(此时 a=3 还尚未写回主内存)
  • 线程 2 从主内存获取最新数据a = 1,b = 3,并写到本人的工作线程
  • 线程 2 最终打印出a=1,b=3

我:这下子我都看明确了,那你给我总结一下为什么会呈现可见性起因吧,万一面试官问我我也好答复。

。。。

造成可见性的起因,次要是因为CPU 有多级缓存,而每个线程会将本人须要的数据读取到独占缓存中,在数据批改后也是写入到缓存中,而后期待刷回主内存,这就导致了有些线程读写的值是一个过期的值。

我:有点 6,我给你先点个赞,那还要一个原子性呢?

原子性我再前面再进行介绍,因为咱们先理解 volatilesynchronized 之后再理解会更简略(你认为我不会 volatile 么,斜眼笑)。明天就先到这里吧,写了这么多,大家都懒得看了。

总结

JMM 这块只是是十分重要的,熟练掌握当前在排查问题、写需要会更加得心应手,本篇原本想再多介绍一些其余内容,然而再写下去篇幅过长,成果就不是很好,所以先介绍这些,这里花 Gie 也强烈建议小伙伴们能亲手敲一下,纸上得来终觉浅,入手敲一敲当前写代码才不会虚。

下一章花 Gie 会持续介绍 happens-beforevolatile 内存构造进阶 等,心愿大家继续关注,今天假期完结了,咱们持续肝

点关注,防走丢

以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花 Gie,有问题大家随时留言探讨,咱们下期见🦮。

文章继续更新,能够微信搜一搜「Java 开发零到壹」第一工夫浏览,后续会继续更新 Java 面试和各类知识点,有趣味的小伙伴欢送关注,一起学习,一起哈🐮🥃。

原创不易,你怎忍心白嫖,如果你感觉这篇文章对你有点用的话,感激老铁为本文 点个赞、评论或转发一下,因为这将是我输入更多优质文章的能源,感激!

正文完
 0