Volatile 详解

C++volatile详解 1. 为什么用volatile?     C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 "The C++ Programming Language" 对 volatile 修饰词的说明: A volatile specifier is a hint to a compiler that a 阅读详情
认识Volatile

首先,java中地Volatile可以解决可见性,指令重排序问题。

例如,以下代码,

package pop.thread.demo3;

/**
 * @author Pop
 * @date 2019/6/18 22:48
 */
public class VolatileDemo {

    public volatile  static boolean volatile_stop = false;
    public static boolean unvolatile_stop=false;
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(()->{
            int i =0;
            while(!volatile_stop){
                i++;
            }
        });
        thread.start();
        Thread.sleep(1000);
        volatile_stop =true;
    }

}

如果我们使用volatile_stop来作为停止线程工作地标签,在一秒后,线程将会完成他地任务,并且退出线程,而unvolatile_stop的改变将不会被让程序停止下来,他将一直运行下去,这里我们可以得到初步地结论。

加了volatile得变量,他们改变对于其他线程是可感知地,也就是说被修饰地变量对于其他线程而言是具有可见的性,而未加地变量如果发生改变,其他线程是感觉不到地,也就是不可见的。

Volatile的意义

如果你cpu核心只有一颗,无论读操作还是写操作都是它去操作,所以它自己是知道此刻这个变量地状态是什么,所以不存在可见性地问题。

当cpu核心数多了后,硬件层面可以支持地线程多起来后,能操作这个变量的人也多了起来,那么当线程A去改变变量地值地时候,线程B又怎么知道此时这个变量是否是最新的值,又或者这个变量会不会发生了变化。

变量明明已经发生了变化,线程B还拿着还未更新的以前地变量去操作,势必会得到错误地预期结果。

从硬件层面谈起

总的来说,能够较大影响一台计算机性能的组件

  • CPU
  • 内存
  • IO设备

当然,我们知道一台计算机性能地高低,取决于三者地共同表现,例如电脑地磁盘的IO操作慢,那么你地内存再大,CPU能够支撑的线程数再多也没有意义。

这三者的处理速度越靠近CPU越快。CPU>内存>IO设备。

同时,为了提高他们各自的性能,也提出了许多地概念并给予了实现。

CPU高速缓存

我一直将CPU使用数据进行计算看做是一个人在吃饭。数据就是面包。

IO设备相当于用来放面包的盘子,内存是我们的手,已经有一部分面包被我们从盘子中拿起,而高速缓存则是我们的嘴巴,它已经在咀嚼送入嘴里地面包了。

我们地最终目的是为了吃面包,而要送入嘴巴过程需要经过盘子(IO设备)、手(内存)、嘴(高速缓存)

。同样我们也很明了,嘴是最容易处理面包步骤,因为我们只需要咀嚼就可以吃饱,而前两者则需要,拿起来,送进嘴里去,这样的步骤。

回到硬件层面,高速缓存的引入也是为了这样的情况,cpu处理速度其实是很快的,但为他加载需要得数据确是漫长得过程,从磁盘到内存,然后在拿内存中的数据进行计算,这过程中发生了阻塞都会影响CPU的计算性能。

为了更好解决这个问题,高速缓存被提了出来,高速缓存可以理解成是存在CPU的一块内存,之前我们说过越靠近CPU,交互的速度也就越快,而高速缓存对于CPU来说就是触手可及的距离。

在这里插入图片描述

当然,我们从任务管理器也可以看到这些缓存的实际大小

在这里插入图片描述

高速缓存中也分了级别,所有线程公用的L3级别缓存,和每个核心都自己独有地L2和L1缓存。

这里,其实我们地CPU性能已经得到一定程度的提升,因为我们每次都会从主内存中将一些数据复制到我们的高速缓存中,这样就可以提高的处理地速度,可是这就存在一个问题,那就是缓存一致性。

缓存一致性协议

其实我们只需要读写数据的时候保持一致就可以了。

最常见的缓存一致性协议就是MESI协议。表示缓存行的四种状态。

  • M(Modify)已修改,表示共享数据存在于CPU缓存中,并且是被修改的状态,也就是缓存的数据和主内存数据不一致。
  • E (Exclusive)表示缓存的独占状态,数据只缓存当前CPU缓存中,并且没有被修改。
  • S(Shared)表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致。
  • I(Invalid)表示缓存已经失效。

在MESI协议中,每个缓存的缓存控制器不仅知道自己的读写操作,而且还需要监听(snoop)其它Cache的读写操作,以方便改变缓存行的状态,和发送缓存行的状态。

对于MESI协议,从CPU读写角度来说,会遵循以下原则。

  • CPU读请求
    • 缓存处于M、E、S状态都可以被读取,意味着如果该缓存行处于已经被修改,已经被独占,已经被多个缓存缓存的时候,可以从高速缓存直接读取。而处于I的阶段,也就是失效的阶段,代表该缓存已经失效,CPU只能从主存中读取。
  • CPU写请求。
    • 缓存处于M、E状态的时候才可以被写。对于S状态的写操作,必须将其他缓存中的存在的该缓存行置为无效才可以写。(I)

所以取决于CPU是否要去高速缓存拿数据还是主内存内存去那数据的关键在于根据MESI协议,该内存行是否已经无效,如果无效,将从主内存拿最新的值,否则就在高速缓存中进行交互。

在这里插入图片描述

缓存管理器负责通知其它缓存在其他CPU中的缓存行是否处于MESI状态的

广播者。

再谈可见性

回到可见性话题上,从上图中我们看到,CPU1对自己高速缓存的修改,对于其它CPU2是不可见的,导致CPU2拿到的数据是脏数据,这是我在前文谈及多次的东西。

但是这里有一个问题,为什么在CPU层面上,已经存在总线锁和缓存锁,当我们执行

package pop.thread.demo3;

/**
 * @author Pop
 * @date 2019/6/18 22:48
 */
public class VolatileDemo {

    public volatile  static boolean volatile_stop = false;
    public static boolean unvolatile_stop;false;
    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(()->{
            int i =0;
            while(!volatile_stop){
                i++;
            }
        });
        thread.start();
        Thread.sleep(1000);
        stop=true;
    }

}

还是没办法让程序一秒后停止呢?

也就是为什么还需要volatile关键字,为什么还会存在可见性问题呢?

MESI优化所带来的可见性问题

之前我们说过,通过MESI协议可以保证硬件层面的可见性。

但是,由于MESI协议中将一个S(Shared)状态的变量变成M(Modify)状态的时候,需要将其他处理器中的缓存设置为I(Invalid)。

而每个缓存行拥有自己的缓存管理器,他将发送通知指令告诉其他CPU,这条缓存已经失效,你们需要从主存获取最新的值。

问题就在这里,当CPU通知其它CPU这条缓存行失效的并收到反馈的这个过程中,CPU是处于阻塞状态的,这也是一个性能的瓶颈。而为了优化MESI协议,工程师将 store bufferes引入到CPU中。

store bufferes其实就是位于cpu和高速缓存中的另一块内存区域,由于发送缓存无效的请求期间CPU阻塞,所以我们将发送请求的任务交与store bufferes,也就说,当修改缓存行内容的时候,把通知其它cpu该缓存失效的任务让store bufferes,去执行,cpu接着执行下面的任务。类似异步的任务。

在这里插入图片描述
这种优化存在两个问题

  • 数据什么时候提交不确定,因为这是一个异步请求,即便是交个了storebuffer来做,其它cpu回复我己经设置缓存行无效的时机我也不确定。
  • 引入了storeBuffer后,处理器会先从storebuffer从读取,如果有数据就从stroreBuffer中读取,否则就从缓存行读取。
int value = 2; //共享状态 S
void cpu0(){
    value = 10; 
    flag = true; //独占状态 E
}
void cpu1(){
    if(flag){ // 这个时候还未收到失效通知
        assert value == 10;
    }
}

对于CPU0而言,他缓存了value(S),和flag(E)。

当CPU0执行value=10的时候,value由S变成了M,CPU0将通知写入到store bufferes中,让他代为转发。接着执行flag=true。

由于flag在CPU0是独占状态,可以直接修改,不需要写入到storeBuffer中。

这个时候之前value=10失效通知还在store bufferes中,他也在等待其他cpu等待通知。

这个时候cpu1发起读请求,因为flag是E,可以直接从高速缓存中读取,所以flag=true可以通过,当去运行assert value == 10;的时候,恩很有可能,CPU1没有受到CPU1storebuffer的通知,而还是用原来的值进行计算,那么这个时候value的值应该是2,所以判断不等于10。

这其实就是我们总说是CPU的乱序执行,也可以说是重排序。他们将会带来可见性问题。

很明显,这就回到了一开始的缓存一致性的问题上,我们仿佛进入了一开始的死循环。

从软件层面谈起

既然硬件层面无法解决,我们可不可以在软件层面做尝试呢?

工程师认为,貌似我解决了CPU层面上的缓存一致性问题,而在软件层面,你们又存在指令的乱序执行,这我很难办,不如我给你定义规范,你们按照自己的语言模型各自实现算了。

在CPU这边,提出了内存屏障的概念。

所谓的内存屏障,就是将store bufferes中的通知缓存行的失效等指令,直接写入到主存中去,明确规定,你们就是要在主存上读写,不要再各自的高速缓存区域玩了。以此来达到可见性问题。

memory barrier在X86的系统上有三种

  • StoreMemoryBarrier(写屏障),在处理任何的write操作之前,强制将存储到store bufferes中的数据同步到主内存,也就是说,在写屏障之前的所有指令对于屏障之后的所有读写指令都是可见的。
  • LoadMemoryBarrier(读屏障),在读屏障之后的所有读操作,必须在读屏障之后执行。配合写屏障,让写屏障所有的内存更新对于读屏障是完全可见的。
  • FullMemoryBarrier(全屏障),算是懒人版本,他可以保证内存读写操作提交之后,再执行屏障后的读写操作,保证屏障后的读写操作使用的是最新数据。
再回到之前的案例
int value = 2; //共享状态 S
void cpu0(){
    value = 10; //写屏障,强制将stroebufferes的内容更新到主存
    storeMemoryBarrir();
    flag = true; //独占状态 E
}
void cpu1(){
    if(flag){ 
        //使用读屏障,从内存中直接获取
        loadMemoryBarrier();
        assert value == 10;
    }
}

所以,内存屏障是为了解决CPU对内存的乱序执行而提出的,用来保证共享数据在多线程并行执行下的可见性。

JMM

JMM全程是Java Memory Model。那么什么是JMM呢

其实上面说了那么多,基本就是提出一个新的解决方法,而产生一个新的问题,然后解决提出解决这种问题的技术,然后又出现了另外的技术。

而前面通过分析我们可以看出,store bufferes虽然可以解决等待其他CPU等待的回复的阻塞问题,但是也导致了软件层面的指令重排序和有序性问题。

那么JAVA作为一款一次编写,到处运行的语言,自然需要设置自己规则来让自己的语言避免重排序,有序性问题。

JMM是一种语言级别的抽象内存模型,也就是对硬件模型的抽象,JMM将保证,由JMM规范后的指令,输入到CPU的执行的时候,是不会重排序,是有序性的。

它定义了共享内存中多线程程序读写操作的行为规范,在虚拟机中把共享变量存储到内存以及从内存读取的时候共享变量的时候该如何保证有序性。

通过这些规则来规范对内存的读写操作而保证指令的正确性,它解决CPU的多级缓存,处理器优化,指令重排序导致的内存访问问题,保证了并发场景下的可见性。

但是!!!!

对于JMM而言,他只是对硬件层面的抽象内存模型,方便java程序在上面更改的适配,从而解决运行的兼容问题。

JMM抽象模型分为主内存和工作内存;主内存是所有线程共享的,一般是是实例对象、静态字段,数据等对象存储在堆内存中的变量。

工作内存是每个线程独有的,线程对变量的操作直接在工作内存中完成,不能直接操作主内存,线程之间共享变量值的传递都是基于主内存在完成。

在这里插入图片描述

可是JMM并没有限制执行引擎使用CPU的寄存器或者告诉缓存来提升执行顺序,也没有限制指令进行重排序。

也就是说。JMM只是将底层的问题抽象到了JVM层面,也就是软件层面,在基于CPU层面提供的内存屏障指令,以及限制编译器的重排序来解决并发问题。

既然硬件层面的缓存一致性问题,和指令重排序问题,那么在JVM这个层面也同样存在。

而Java内存模型底层实现可以简单的问题:通过内存屏障(memory barrier)禁止重排序,即编译器将会更具硬件底层体系架构,将这些内存替换成具体的CPU指令。

对于编译器而言,内存屏障将会限制它的重排序优化。

对于处理器而言,内存屏障将会导致缓存的刷新操作,而保证读写操作对其它线程可见。

例如,对于volatile,编译器将在volatile字段的读写操作前后插入一些内存屏障。

JMM是如何解决可见性有序性问题

JMM提供了禁用缓存,以及禁止重排序的方法,来解决可见性和有序性问题。

volatile、sychronized、final。

从源代码到最终执行的指令,可能会经过三种重排序。

源代码->1.编译器级别优化重排序->2.指令级并行重排序->3.内存系统指令重排序->最终执行指令序列。

1和2属于处理器重排序,这些重排序可能会导致可见性问题。

编译器的重排序:JMM明确禁止在某个位置进行重排序操作。

处理器重排序:JMM要求编译器生成指令的之后,会插入内存屏障来禁止处理器重排序。

不能重排序的地方

不过,也并不是所有的程序都会出现重排序问题。

编译器和CPU的重排序会遵循依赖性原则。编译器和处理器不会改变数据依赖关系。

a=1;b=a;//1
a=1;a=2;//2
a=b;b=1;//3

如果单线程对1,2,3种情况进行重排序,会导致结果不一样,所以编译器不会这类指令进行优化。这也被称为 as-if-serial。

int a = 2;//1
int b = 3;//2
int c = a+b;//3

1和3,2和3存在依赖,所有3的位置一定是在1,2之后,不然编译无法通过。但是1和2却可以重新排序,因为他们的执行顺序不一致也不会影响结果。

JMM层面的内存屏障。

在这里插入图片描述

HappenBefore

他表示前一个操作结果对于后续的操作是可见的,这不是一个技术,而是一个概念,添加了内存屏障后,将保证屏障前的数据的读写对屏障后的读写可见。

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须存在happens-before关系,这来两个操作可是同一个线程,也可以是不同的线程。

JMM中哪些方法建立了happend-before规则

程序顺序原则

class VolatileExample{
    
    int a = 0;
    volatile boolean flag = false;
    public void writer(){
        a = 1;      //1
        flag = true; //2
    }
    public void reader(){
        if(flag){     //3
            int i =a; //4
            //...
        }
    }
    
}

1 happens-before 2;3 happens-before4;因为这个顺序不管如何变,结果都是一样的。

volatile 变量规则

对于volatile的修饰的变量写操作,一定happen-before后续的对于volatile变量的读操作。

所以上面的代码,2 happens-before 3

传递性规则

还是以上面的代码为例子,如果 1 happens-before 2;3 happens-before 4;那么根据传递性,1 happens-before 4

start 规则

public StartDemo{
    
    int x = 0;
    Thread t1 = new Thread(()->{
        
        //t1.start()之前的操作一定优先于t1内的操作
        //所以这里的x是10,而不是0
        print(x);// 10
    });
    x = 10;
    t1.start();
}

在线程启动前的数据将会保证一定会在后续线程得到一定是最新的。

join 规则

Thread t1 = new Thread(()->{
   // 此处对共享变量x修改
    x = 100;
});
t1.start();//内部已经对x进行修改
t1.join();//子线程对共享变量的修改,对join之后可见。
print(x);// 100

监视器锁的规则

对一个锁的解锁 happens-before 于随后这个锁的加锁。

synchronized(this){//此处自动加锁 2
    //x 是共享变量,初始值为10
    if(this.x<12){
        this.x = 12;
    }
    
}// 此处自动解锁  1

线程A进入这个模块加锁,并且离开后解锁,对于下一个线程B而言,线程A的解锁一定 happens-before与线程B的加锁。

总结

我们从硬件层面和软件层面分析了Volatile的由来。虽然Volatile的使用非常简单,但是短短几个字背后其实也有很复杂的实现来支持。

总而言之,Volatile是JAVA层面提供的一种内存屏障的实现,他将会保证被Volatile修饰的变量的读操作一定有写操作,一定优先于后续的读操作,而保证后续读到的变量值一定是最新的,使用JMM的内存屏障,而保证可见性问题。

  • 硬件层面
    • 为了提高cpu的性能,我们加入高速缓存。
    • 由于高速缓存与主内存的交互存在缓存一致性问题,我们加入了总线锁和缓存锁,缓存锁中是比总线锁锁粒度更小,性能更好的锁技术,里面使用了MESI协议。
    • 又因为,MESI协议告知其它处理器缓存失效的等待回复中,会导致处理器阻塞,有性能问题,所以我们加入了store buuferes。来异步支持通知。
    • 最后,异步由于无法保证其它处理区正常接受失效的通知,又再次导致可见性问题和编译器的指令重排序。问题仿佛又回到了起点,所以cpu设置了内存屏障,让编译器自己实现各自的内存屏障实现。
  • 软件层面
    • JMM的提出,隔离了硬件层面的模型,JAVA 专用的内存模型。JMM的实现其实是为了让编译器按照自己的规则,禁止对某些指令进行重排序,对CPU而言,JMM会添加java自己的实现的内存屏障来到指令中,让cpu执行,从而避免可见性问题。所以JMM可以看做是一个中间人,他规范了JVM的执行规则,也告诉了硬件改如何执行这些指令。
    • Volatile 是JMM 中 happens-before规则中的一种,被他修饰过的变量,变成指令后,将会前后增加对应的内存屏障,添加内存屏障的工作由JMM来实现。

每天积累一点点

Java volatile详解 一 简介 volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。 二 并发编程的3个基本概念 1.原子性 定义: 即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 阅读详情

相关推荐

ModelDesign

设计模式源码,附带一篇博文blog.liujunliang.com.cn

unity--修饰符

unity 修饰符

学海无涯 1619

从零构建:Simulink与PX4的快速原型开发实战

本文详细介绍了如何利用Simulink与PX4进行四旋翼无人机的快速原型开发,涵盖环境搭建、动力学建模、控制器设计、仿真验证及自动代码生成全流程。通过实战案例展示姿态控制器的实现与调试,帮助开发者高效完成从算法设计到硬件部署,显著提升开发效率并降低试错成本。

plant的博客 323

深入并发-内存模型

从硬件层面了解可见性 一台计算机中最核心的组件是 CPU、内存、以及 I/O 设备。在整个计算机的发展历程中,除了 CPU、内存以及 I/O 设备不断迭代升级来提升计算机处理性能之外,还有一个非常核心的矛盾点,就是这三者在处理速度的差异。CPU 的计算速度是非常快的,内存次之、最后是 IO 设备比如磁盘。而在绝大部分的程序中,一定会存在内存访问,有些可能还会存在 I/O 设备的访问。 为了提升计算...

岁月安然 458

脑机接口最新发展动态报告 (2024-2025)

脑机接口动态

高山 4105

SikuliX实战:5分钟搞定游戏挂机脚本(附防检测思路与Python集成技巧)

本文详细介绍了如何使用SikuliX快速开发游戏挂机脚本,包括环境配置、基础原理、防检测策略及Python集成技巧。通过实战案例展示如何实现自动化任务、高级防检测方案,并结合Python提升脚本性能与智能度,帮助玩家高效完成重复性游戏操作。

weixin_33228769的博客 334

Unity的三种单例模式(饿汉,懒汉,MonoBehaviour)

​ 对象全局唯一,且经常被使用。学过面向对象的小伙伴对static想必都不陌生,其具有的特质和今天要讲解的单例十分相似,自然后续的单例模式也会使用到。刨根问底:static属性为何类中唯一共享?​ C#中创建的所有类都会存在一个全局唯一的类型对象(System.Type),类型对象中会保存此类的函数表,静态字段等等,也就是说其实静态字段存储在全局唯一对应的类型对象中,而不是存在于此类new出来的实例对象中,现在就能很好的解释静态字段两点性质啦。为了更好的实现代码复用,以下三种单例模式均会采用工具类的设计

Q540670228的博客 6418

JavaFX - 动画

JavaFX 动画

思你日度难眠 1601

volatile

volatile关键字整理

Hanyinh的博客 372

ChatBI告别“NL2SQL依赖症”!从准确率50%到90%,3大技术路线+4个企业案例拆解核心玩法

摘要: ChatBI通过自然语言交互实现数据民主化,其核心是“语义理解+数据执行+知识沉淀”的全链路能力,而非单一的NL2SQL技术。行业采用三大技术路线提升准确率:专有知识库(提升30%)、指标标签语义层(准确率85%)、JSON中间层(多轮对话提升25%)。数据执行方案包括NL2SQL、NL2API等,需结合场景选择。企业落地需解决四大痛点:跨表查询准确率低(引入拓扑图)、模糊查询(用户干预机制)、响应慢(预计算缓存)、多轮对话(上下文池)。Spring AI Alibaba提供轻量级集成方案,助力企业

weixin_52610848的博客 1652

qt工程引入opencv库步骤

第一步:新建qt工程 第二步:在工程名.pro文件中添加opencv库的路径                   INCLUDEPATH += /home/liyuanzhe/opencv-2.4.8/include/opencv \ /home/liyuanzhe/opencv-2.4.8/include/opencv2 LIBS += /home/liyuanzhe/open

superyuanzhe的专栏 5722

终极指南:HybridCLR Volatile关键字如何彻底解决Unity热更新的多线程内存可见性问题

HybridCLR是一个特性完整、零成本、高性能、低内存的Unity全平台原生c#热更方案,为开发者提供了高效可靠的热更新解决方案。在多线程环境下,内存可见性问题常常困扰着Unity开发者,而Volatile关键字正是解决这一问题的关键。本文将深入探讨HybridCLR中Volatile关键字如何彻底解决Unity热更新的多线程内存可见性问题,帮助开发者轻松应对热更新中的多线程挑战。 ## 一、

gitblog_00608的博客 844

C语言图形化界面——含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)

本文借助easyx库函数(提供下载链接),很方便地用C语言实现了鼠标、按钮、进度条、坐标尺等操作,为普通C程序的友好人机交互界面提供了一种简单便捷的方案。

体会编程语言独到的美 17万+

volatile详解

一. volatile的内存语义 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内中 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量 所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取 二.volatile变量的读写过程 Java内存模型中定义的8种工作内存与主内存之间的原子操作: read(读取) -> l...

枫陵的博客 1万+

动画实例化:千人同屏不卡顿的秘密

是一种将大量相同骨骼结构的角色动画批量处理、批量渲染的技术。动画计算与渲染解耦,动画数据在CPU或GPU上批量计算,渲染时通过GPU Instancing一次性绘制大量角色。极大减少DrawCall,提升大规模角色(如MMO、RTS、群体AI等)动画的运行效率。动画Bake:编辑器下将所有动画片段Bake为骨骼矩阵纹理。实例参数管理:每个角色维护自己的动画状态、帧索引等。批量渲染:用DrawMeshInstancedIndirect等API批量渲染,Shader中采样骨骼数据。剔除与LOD。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 418

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

volatile关键字详解

缓存一致性协议: 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。在多线程环境下,CPU轮流切换线程执行,有可能某个线程修改了数据,准备回主内存,此时CPU切换到另一个线程修改了数据,并且写回到了主内存,此时就导致数据的不准确。的作用就是,当某个线程工作内存中的值与主内存中的值,如果不相同就会一直。

努力分享一些干货 1837

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

volatile 详解

内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。...

GeGe&YoYo的博客 338

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

volatile详解、原理

volatile详解、原理、作用;并发编程;线程可见性、禁止指令重排序;JMM

qq_44700578的博客 1897

Volatile详解

上述提到MESI机制中,判定缓存失效主要是通过通知机制实现的,通知的方式存在一个潜在问题是,如果当前cpu没有收到失效通知,误以为自身是最新数据进行运算,实际上因为缓存已经失效,只是通知还没到,这就造成了潜在的资源浪费和cas延迟。一个线程在释放锁时,所有对共享变量的修改都会被刷新到主内存中,而其他线程在获取锁时,会从主内存中读取最新的值。以上这是表象描述,这里底层实现可能会根据缓存一致性协议来确保,比如会结合通知、嗅探等机制来提高效率,避免每次的直接主存访问,不过这关系到处理器的技术实现了。

2403_88969259的博客 1337

Java中volatile详解

絮叨 这一篇也算是Java并发编程的开篇,看了很多资料,但是轮到自己去整理去总结的时候,发现还是要多看几遍资料才能完全理解。还有一个很重要的点就是,画图是加深印象和检验自己是否理解的一个非常好的方法。 一、Volatile怎么念? 看到这个单词一直不知道怎么发音 英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl] adj. [化学] 挥发性的;不稳定的;爆炸性的;反复无常的 复制代码 那Java中volatile又是干啥的呢? 二、Java中volatile用来干啥...

Mick_^_^的博客 1683
上一篇: Synchronzied 的深入理解(下)
下一篇: Linux 下安装 mysql 数据库简单教程
PopCandier
博客等级 码龄9年 19粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值