我所理解的JMM-- 1概述

本文探讨了Java Specification Requests(JSR)的概念及其在Java Memory Model(JMM)中的作用,对比了消息传递与共享内存模式的优缺点,重点分析了共享内存模式下JMM如何解决多线程通信中的可见性、处理器优化等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JSR 是什么?

JSRJava Specification Requests全称,用于制定java规范的东西。我们的JMM就是制定在JSR中。本文仅讨论JSR133.

JMM是什么

JMMJAVA Memory Model ,它定义了虚拟机编译器所需要遵守的规则。

JMM存在的意义

多线程通信方式大体上分为两种形式:
1.消息传递
2.共享内存

两者分别有各自的优劣:
(1)消息传递:往往依赖于事件循环,效率略低,但不需要考虑多线并发问题,如Dart语言便是其中一个。
(2)共享内存:多线程共享一个内存区域,效率高,但是往往带来大量的并发问题,如 缓存带来的脏数据,原子更新等,如JavaC/C++语言

由于Java是多线程通信采用的共享内存方式,所以必须制定一些标准,解决并发相关问题。

JMM主要解决 编译重排序问题,和处理器编译重排序问题。内存缓存问题也可以理解为内存重排序问题。

消息传递模式

java中的消息传递模式可使用如下代码完成:



public class Main {
    public static void main(String[] args) throws InterruptedException {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        ServerSocket serverSocket = new ServerSocket(9998);
                        Socket socket = serverSocket.accept();
                        InputStream inputStream = socket.getInputStream();
                        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                        String msg = null;
                        while ((msg = br.readLine()) != null) {
                            System.out.println("服务端线程收到" + msg);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        try {
            System.out.println("客户端启动");
            Socket socket = new Socket("127.0.0.1", 9998);
            OutputStream outputStream = socket.getOutputStream();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
            bw.write("你哈\r\n");
            bw.flush();
            TimeUnit.MILLISECONDS.sleep(300);


        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上述代码存在两个线程一个是Main线程,和一个服务端子线程,两者使用本地socket进行通信。两个线程并无共享变量代码,所以你根本不需要考虑所谓的原子性,有序性,可见性相关问题。但是相率可见一斑

共享内存模式

由于Java天生就是此模式,所以例子非常多。所以我们直接举例一些共享内存所带来的的问题。

可见性,处理器优化问题

public class Main {
    static boolean isStop = false;
    public static void main(String[] args) throws InterruptedException {
        Thread childThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isStop) {
                }
                System.out.println("Child Thread  over");
            }
        });

        childThread.start();
        TimeUnit.SECONDS.sleep(1);
        isStop = true;
    }
}

上述代码可能永远无法输出Child Thread over。为什么?如果回答不上,这便是学习JMM的重要性。这里简单说下出现的原因,和解决方案,但是只是简单介绍。

网上解释:
(1)由于每个线程都有自己的共享变量的副本,而主内存的更新了,但是childThread线程副本没有更新。
(2)主线程Main中修改 isStop,并没有刷新回主内存,而是存在Main线程中。
(3)解释执行时处理器优化,childThread运行时大量判断标识变量然后执行空循环,且在过段时间其他线程才修改了标识变量isStop.所以处理器,会直接跳过判断标识变量isStop然后直接方法体。

解决方案:
(1)内部添加存在并发语法的代码如printlnsynchronized等,所以你会发现内部加了一个输出也会停止循环。原因是println内部存在并发语法关键字synchronized,而这个关键字会触发缓存更新,且使执行器不轻易的优化跳过表示变量的判断。
(2)共享变量isStop添加修饰符volatilevolatile会是缓存失效,每次都能读取到最新数值,且写入立即刷新到主内存。由于volatile存在也会使执行器谨慎的优化 不会出现第三点。

关于第三点可以参看如下链接:
-server多线程由于可见性导致死循环
Java volatile 汇编代码研究

final语义于安全构造对象问题

JSR133标准之前的JDK,会有一个非常著名的多线程字符串问题。
如下:

public class Main {
    static volatile String msg = "";
    public static void main(String[] args) throws InterruptedException {


        Thread childThread = new Thread(new Runnable() {
            @Override
            public void run() {
                String temp = "I love";
                //(1)FLAG TWO
                msg = temp.substring(1);
            }
        });
        Thread childThread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //(2)FLAG THREE
                //假设此处FLAG TWO已经执行完成
                if (msg.equals(" love")) {
                    System.out.println("true");
                } else {
                    System.out.println("false");
                } 
              
            }
        });

        childThread.start();
        childThread2.start();
    }
}

上面的假设执行(2)FLAG THREE时已经完成 (1)FLAG TWO,那么此处依然可能会输出false.

原因是msg此时的字符串是I love,为什么I love?
由于java字符串类实现是由自身的三个属性构造,分别为数组,数组的起始坐标,字符串长度,三者皆为final。而字符串之间可以共享字符串数组,减少不必要的内存对象。但是字符串构造时,字符串的起始坐标赋值可能位于构造函数之外,此时另一个线程读取了一个还未赋值开始坐标的字符串,具体详解后续文章描绘。


结束:文章大致说了由于共享内存模型的编程语言所带来的弊端,所以孕育而生了JMM.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值