首页 > 系统相关 >JUC:java内存模型(如何保证?可见性、原子性、有序性)

JUC:java内存模型(如何保证?可见性、原子性、有序性)

时间:2024-04-01 19:29:24浏览次数:40  
标签:JUC java run 指令 线程 内存 有序性 volatile 执行

文章目录

java内存模型

JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。

JMM 体现在以下几个方面 :

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

可见性

按照如下代码,一般我们都会想,run改成false,那么线程t1也就运行结束了,但是并没有运行结束,而是无线运行下去了。

这就算因为可见性导致的。

@Slf4j(topic = "c.Test5")
public class st5 {
    static boolean run = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(()->{
            while(run){

            }
        });
        t.start();

        sleep(1);
        run = false; // 线程t不会如预想的停下来
    }
}

在一开始t1线程是在主内存中获取 run 值。
在这里插入图片描述

随着 while 次数一直获取次数增多,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率。

在这里插入图片描述

但是,会带来一个问题,就是主存和内存中的值不一致,当主存内容修改,t1 线程任然从内存中获取run值,就会读到旧的内容,从而一直停不下来。

在这里插入图片描述

解决方法

加volatile, 它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的最新值,线程操作 volatile 变量都是直接操作主存。

volatile static boolean run = true;

当然我么之前用synchronized上锁,让共享变量在锁代码块中操作,也是可以保证可见性的,不然我么之前的的代码不就都不对了么。

不过synchronized毕竟比较重还要创建monitor,而volatile是更加轻量级的。

原子性

volatile只能保证读取到最新值,而不能解决原子性问题。

比如线程t1读取到了值x = 0,要进行++操作时,正好切换了,切换到了线程t2了,而t2也读取x = 0, 并对于x进行了–操作返回给内存进行更新,然后又切换回线程t2,t2该进行++操作,并且赋值x更新到内存。

原本一加一减应该变回0,但经过这种线程切换的情况,x最后为1,显然不符合我们的预期,因为破坏了读写的原子性。

所以在保证原子性时,还是利用synchronized和ReentrantLock编写代码时对共享变量读写保证其原子性。

有序性

指令重排

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序。

比如i和j同时++,因为互不影响,谁先执行都一样,所以jvm可能的执行顺序就是i++,j++也可以是j++,i++。

流水线技术

流水线技术是一种并行计算的方式,它类似于生产流水线,将一个复杂的任务分解为一系列的子任务,并且这些子任务可以并行地执行,每个子任务的输出作为下一个子任务的输入,从而实现整体任务的并行处理。

在计算机科学中,流水线技术通常用于优化处理速度,特别是对于那些可以被分解为多个相互独立阶段的任务。通过流水线技术,可以将一个任务分解为多个阶段,每个阶段由专门的处理单元来执行,这样就可以同时处理多个阶段,从而提高整体处理速度。

流水线技术常见于处理器的设计中,例如现代CPU中的指令流水线。在指令流水线中,CPU将指令执行分解为多个阶段,比如取指阶段、解码阶段、执行阶段等等,每个阶段由专门的硬件单元来执行,这样就可以实现多条指令的并行执行,从而提高CPU的吞吐量。

简单来说啊,在不改变结果的情况下,把一个任务,拆成多个,重排序,一起执行。

在这里插入图片描述

// 可以重排
int a = 10; // 指令1
int b = 20; // 指令2
System.out.println( a + b );

// 不能重排
int a = 10; // 指令1
int b = a - 5; // 指令2

指令重排出现的问题

int num = 0;
boolean ready = false;

// 线程1 执行此方法
public void actor1(I_Result r) {
    if(ready) {
        r.r1 = num + num;
    } else {
        r.r1 = 1;
    }
}

// 线程2 执行此方法
public void actor2(I_Result r) { 
    num = 2;
    ready = true; 
}

r.r1最后可能为0,但是概率非常非常非常小,我们自己测不出来的。

原因线程2执行方法指令重排,先执行了ready = true,这时切换线程1,执行了r.r1 = num + num,然后线程2才执行了num = 2。那么最后r.r1就等于0.

解决方法
还是加上volatile即可,可以防止指令重排序。
volatile会保证 顺序性 和 可见性。

模式之Balking(犹豫)

犹豫模式,一个线程在执行某个操作之前会检查某个条件,如果条件不满足,则放弃执行,直接返回。这个模式的核心思想就是避免重复执行某个操作,当发现已经有其他线程或本线程已经执行了相同的操作时,就直接结束当前线程的执行。
Balking 模式通常适用于一些需要进行资源共享或状态同步的场景。当多个线程需要对共享资源进行访问或修改时,可以使用 Balking 模式来确保资源的正确使用,避免出现竞态条件或资源浪费。

举个简单的例子,假设有一个线程池,多个线程需要从线程池中获取任务执行。在获取任务之前,每个线程会检查线程池中是否还有可用的任务,如果有,则取出任务执行,如果没有,则放弃执行,直接返回。这样就可以避免多个线程同时尝试获取任务导致的竞态条件,同时也可以避免线程在没有可执行任务时进行无效的等待,提高了线程池的效率。

public class MonitorService {
    
    // 用来表示是否已经有线程已经在执行启动了
    private volatile boolean starting;
    
    public void start() {
        log.info("尝试启动监控线程...");
        synchronized (this) {
            if (starting) {
                return;
            }
            starting = true;
        }
        
        // 真正启动监控线程...
    }
}

这里volatile是可以不加的,synchronized已经保证了可见性,不过,通常建议还是添加 volatile 关键字来明确地表达你的意图,即使这样做可能会带来一些微小的性能开销。

标签:JUC,java,run,指令,线程,内存,有序性,volatile,执行
From: https://blog.csdn.net/Cosmoshhhyyy/article/details/137229583

相关文章

  • java计算机毕业设计(附源码)医患辅助系统(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:随着信息技术的飞速发展,医疗健康领域正经历着前所未有的变革。传统的医患交流模式受限于时间和空间,难以满足现代社会对医疗服务效率和质量的要求。医患辅......
  • java计算机毕业设计(附源码)医疗大数据系统(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:医疗大数据系统是近年来在医疗领域内兴起的一个重要研究方向,它利用现代信息技术手段,对海量的医疗健康数据进行采集、存储、管理和分析,以期提供更为精准、......
  • Java版商城:Spring Cloud+SpringBoot b2b2c电子商务平台,多商家入驻、直播带货及免 费
    随着互联网的快速发展,越来越多的企业开始注重数字化转型,以提升自身的竞争力和运营效率。在这个背景下,鸿鹄云商SAAS云产品应运而生,为企业提供了一种简单、高效、安全的数字化解决方案。鸿鹄云商SAAS云产品是一种基于云计算的软件服务,旨在帮助企业实现业务流程的自动化和优化。......
  • Java中的运算符有哪些类型
    目录算术运算符比较(关系)运算符逻辑运算符位运算符赋值运算符特殊运算符总结在Java语言中,运算符是用于执行特定操作的符号,比如算术运算、比较或逻辑运算。Java中的运算符可以大致分为以下几类:算术运算符比较(关系)运算符逻辑运算符位运算符赋值运算符特殊运算符(如条件......
  • Java中文乱码浅析及解决方案
    Java中文乱码浅析及解决方案引言一、中文乱码现象描述1.1什么是中文乱码?1.2中文乱码产生的原因二、中文乱码的产生场景2.1控制台输出乱码2.2文件读写乱码2.3网络传输乱码2.4数据库存储乱码三、解决中文乱码的方法3.1统一编码为UTF-83.1.1系统环境设置3.1.2编......
  • java计算机毕业设计(附源码)一起捞餐厅点餐和管理系统(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在现代都市的快节奏生活中,餐饮业作为人们日常消费的重要组成部分,扮演着至关重要的角色。随着科技的不断进步和消费者需求的多样化,传统的餐饮服务方式已经......
  • java计算机毕业设计(附源码)一校置之系统的开发实现(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在信息化时代背景下,教育行业正经历着前所未有的变革。传统的教育管理方式已无法满足现代高效、精准的管理需求。随着计算机技术和互联网的发展,一校置之系......
  • 【Java】使用 Java 语言实现一个冒泡排序
    【Java】使用Java语言实现一个冒泡排序前言上一篇文章已经学习了,如何使用IDE集成开发工具编写Java代码,并输出了一段HelloWorld的代码。本篇文章将通过IDE使用Java语言实现一个冒泡排序。冒泡排序介绍冒泡排序也是一种简单直观的排序算法。冒泡排序的基本思想是多次遍历......
  • 【网络原理】使用Java基于TCP搭建简单客户端与服务器通信
    【网络原理】使用Java基于TCP搭建简单客户端与服务器通信  ......
  • [附源码]JAVA计算机毕业设计电脑小白网站(源码+开题)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容JAVA计算机毕业设计电脑小白网站的研究背景、意义、目的、研究内容、拟解决的主要问题、研究方案和预期成果一、研究背景随着信息技术的迅猛发展,计算机已成......