Volatile内存屏障&指令重排

深入刨析volatile及内存屏障 指令重排序的意义:JVM能根据处理器特性(CPU多级缓存、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。在编译器与CPU处理器中都能执行指令重排优化操作。 阅读详情

前言

在上一篇笔记中提到了volatile关键字的作用,我们知道并发编程的三大理论是可见性、有序性和原子性,而volatile只能保证可见性和有序性,有序性是什么呢?有序性就是让程序能够有序的执行,volatile可以禁止指令重排,但是指令重排在应用中其实很常见的,比如简单的例子就是当cpu在执行一条指令的时候,执行完发现这个值已经被修改了,是处于无效的状态,这是cpu缓存架构中的缓存一致性协议中MESI协议,那么这个时候寄存器需要从新加载变量,那么在加载的过程中,cpu是不会闲着的,所以这个时候cpu会去执行其他指令,所以说cpu的执行时乱序的,也就是指令重排了。

内存屏障

硬件层提供了一系列的内存屏障 memory barrier / memory fence(Intel的提法)来提供一致性的能力。拿X86平台来说,有几种主要的内存屏障:1. lfence,是一种Load Barrier 读屏障2. sfence, 是一种Store Barrier 写屏障3. mfence, 是一种全能型的屏障,具备lfence和sfence的能力4. Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。
内存屏障有两个能力:1. 阻止屏障两边的指令重排序2. 刷新处理器缓存/冲刷处理器缓存
对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存
Lock前缀实现了类似的能力,它先对总线和缓存加锁,然后执行后面的指令,最后释放锁后会把高速缓存中的数据刷新回主内存。在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。
不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。通过以上可以知道java中的关键字volatile其实是一种lock屏障,lock本身不是内存屏障,但是它能完成类似于内存屏障的功能。
上面的解释简单来说就是增加了内存屏障过后,就是在操作本身的变量之前,能够看到在主存中的最新值,可以刷新cpu的高速缓存。

CPU缓存架构之伪共享

伪共享是什么呢?我们在之前的笔记中讲到,cpu中的缓存一致性协议是锁的缓存行,但是在cpu缓存架构中,如果我们多线程频繁的对一些变量进行频繁操作,那么线程之间的数据就频繁失效,频繁的去主存中加载;前面说了缓存行的大小最大为64byte,那么如果多个线程都在同一个缓存行中频繁操作,那么就会出现缓存失效,然后又要重新加载,我们将这种不合理的资源竞争的关系称为伪共享,具体我们可以看下图:
在这里插入图片描述
如上图所示,当启用了缓存一致性协议的情况下,主存中有两个变量x和y,有两个线程都在操作x和y,而x和y都处于同一缓存行中,那么这个时候如果Threa1操作了x,那么总线嗅探机制嗅探到过后Thread2发现自己的x失效了(MESI),又重新加载x,而Threa2修改了y过后,Thread1又要重新加载y,这样反反复复的操作就是称为伪共享,那么如何避免这种伪共享呢?伪共享是会有性能开销的,那么我们如何来避免呢?我们知道缓存一致性协议锁的是缓存行,而缓存行默认是64byte,那么我们是不是可以通过占满一个缓存行来达到目的呢?避免重复的去主存加载最新的值,比如我们的一个类里面有两个long的变量,我们知道long占8byte,那么我们每次操作的时候将缓存行占满是不是就可以避免伪共享了,占满过后,每次操作就是自己共享一个缓存行,那么这样就可以避免伪共享的出现:

public class T0918 {


    public static void main(String[] args) throws InterruptedException {
        Ponint ponint = new Ponint();
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(()->{
            for(int i = 1; i<= 100000000 ; i++){
                ponint.x ++;

            }

        },"Thread-t1");

        Thread t2 = new Thread(()->{
            for(int i = 1; i<= 100000000 ; i++){
                ponint.y ++;

            }

        },"Thread-t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        System.out.println("耗时="+(end - start));

    }


    static  class Ponint{


        public volatile  long x;
       
        public volatile  long y;

    }
}

执行完成过后如图:
在这里插入图片描述
差不多3s多是吧,那么我们来修改下程序,修改下类Point如下:

static  class Ponint{


    public volatile  long x;
    long p1,p2,p3,p4,p5,p6,p7;
    public volatile  long y;

}

在这里插入图片描述
是不是少了2s中,为甚呢?就是上面所说的我们通过添加7个long变量来防止cpu缓存架构出现伪共享,你想哈,如果存在伪共享,那么是不是两个线程每次执行的时候都要出现重新从主存加载,那么这个时候就会性能损耗,当然耗时也比较多;但是在实际的开发过程中,我们这样写的话有点不太好看,别人读的你的代码就发现有点膈应,在JDK8中其实提供了一种实现,JDK8提供了一种注解来标明可以占满缓存行,但是需要增加启动参数-XX:-RestrictContended
在这里插入图片描述
是不是也一样的能够达到效果啊,所以这个例子就是伪共享的发现与解决。

指令重排

指令重排在上面已经大概介绍了下都知道在java中通过volatile关键字可以避免指令重排,那么指令重排用白话文该怎么解释呢?我这边用一个简单的自己也是平时很常见的来说明下,比如大家在开发过程中或者读框架底层源码都知道单例模式的懒汉模式中有个双重检查(doublechaeck)

public class SingleFactory {

    private SingleFactory(){}

    public static volatile SingleFactory factory;

    public  static SingleFactory getInstance(){
        if(factory == null){
            synchronized (SingleFactory.class){
                if(factory == null){
                    factory = new SingleFactory();
                }
            }
        }
        return factory;

    }
}

上面的双重检查为什么要这样做?加入同时过来几个线程都要得到SingleFactory 的实例,第一重检查可能所有线程都会进,那么synchronized 中的第二重检查只有第一个线程可以进,其他线程发现不为空就会直接返回对象了。但是有没有注意到对象实例factory是增加了vaolatile关键字的,为什么要这样做呢?而在上面的单例模式中,增加这个关键字的目的其实就是为了防止指令重排,大家都知道程序在执行factory = new SingleFactory()的时候分为三步,那三步呢?1.在堆区开辟一块cell空间来存储SingleFactory对象,并获取了内存地址;2.堆factory对象进行初始化;3.将factory对象指向堆区开辟空间的内存地址。比如说在上面的流程中,假如factory对象初始化的逻辑复制,初始化的内容非常多,如果指令重排了,在多线程环境下可能出现3在2还在初始化完就执行了,这个时候指令重排了,那么拿到factory这个对象的线程可能会操作factory的数据,那么这个时候可能还没初始化完,所以就会出问题,所以加了volatile关键字就是为了防止指令重排而带来线程数据安全问题。来聊聊指令重排,声明解释了单例工厂的禁止指令重排,在指令重排的概念里面有两个,as-if-serial和happens-before

as-if-serial

这个是什么意思呢?专业的解释就是不管指令如何重排,在单线程的环境下,不会影响最终的执行结果,编译器、runtime和处理器都必须准守as-if-serial语义,为了准守as-if-serial语义,编译器和runtime、处理器在数据间没有依赖关系的时候对指令进行重排序以达到性能的最优;但是如果数据间存在依赖关系,指令重排序过后会影响最终的执行结果,这样就不能进行指令重排序。什么意思呢?我们来看一段代码:

int a = 3;//A
int b =4;//B
int c = a + b * 4;//C

代码A 、B、C的顺序如上,as-if-serial的意思就是A与C有依赖关系,B与C也有依赖关系,所以C不能再A B之前执行,也就是再怎么重排序,也是必须A和B执行完成过后才能执行C;在比如:

A:
int a=3;
int b=4;
a = a + 3;
int c = a *b;

上面的代码中有4行代码,在编译过后运行的过程中编译器是可以进重排序以达到性能最优,比如我们看第一行代码和第三行代码是可以一并执行的,所有这段是代码是可以进行重排序的,重排序过后不会影响执行结果。那我们再来看如果代码这样写

B:
int a=3;
a = a + 3;
int b=4;
int c = a *b;

假如,我说假如,编译器是顺序执行,不会指令重排,那么到底是A的执行效率高还是B的执行效率高呢?可能有些人认为执行效率一样啊,其实执行效率是B要高,为什么呢?上一篇笔记中我们记录到了CPU的缓存架构就说过,说这个之前,我们先来看一张图:
在这里插入图片描述
CPU缓存即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,减少CPU的等待时间,提高了系统的效率。CPU缓存延迟,单位是CPU时钟周期,可以理解为CPU执行一个指令的时间
上图的意思就是CPU执行一个时钟周期需要从不同的内存中加载数据的时钟周期数,从主存中加载需要167个时钟周期,从高速缓存中加载就很快,时钟周期也很短,所以也就解释了为什么CPU需要L1L2L3高速缓存了,而不直接从主存加载;了解了这个过后,来看下上篇笔记中的一个图
在这里插入图片描述
比如我们执行:int a=3;int b=4;a = a + 3;int c = a *b;首先如果a=3完成过后,执行b=4,然后在执行a=a+3,那么这个时候是不是还要去主存中加载a过来,才能进行运算,那么这个时候又要加载一次,根据上面的cpu时钟周期图可以知道,从主存加载过来是有性能开销的,所以虽然这段代码不影响执行结果,但是如果不重排序的话,性能肯定没有B段代码的性能高的;当然了,在正常的执行过程中这段代码是要进行指令重排序的,这样做的性能将会是最优的。

happens-before

先行发生原则,从JDK1.5开始,happens-before用来表述在多线程之间的内存可见性,在JMM模型中,如果一个线程的操作必须对另一个线程可见,那么这两个线程必须存在happens-before关系;happens-before原则非常重要,它是判断数据是否竞争,线程数据是否安全的重要依据,依靠这个原则,我们解决两个线程是否存在资源冲突的所有问题,简单来说就是如果线程执行过程中修改的数据必须对其他线程可见,那么线程之间执行就必须存在happens-before关系;happens-before原则如下:1.如果一个操作happens-before另一个操作,那么第一个操作的执行结果必须对第二个操作可见,而且必须是第二个操作在第一个操作之前;2.如果两个操作之间存在happens-before关系,并不意味着两个操作非要按照happens-before来执行,编译器优化过后的指令重排序只要不影响执行结果,那么也是可以的。这边定义了happens-before的的原则:

1.程序次序规则:在一个线程中,按照代码的顺序,编写在前面的先执行,
编写在后面的后执行;

2.锁规则:在多线程中,某个线程在某个时刻unlock操作必须在lock操作之后执行;

3.volatile规则:对一个变量的写操作先行发生与后面线程对这个变量的读操作;

4.传递规则:如果操作A先行发生与B,而B又先行与发生C,那么A是先行与发生C;

5.线程启动规则:线程的Thread的start方法先行与线程内的任何操作;

6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断
线程的代码检测到中断事件的发生;

7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,
我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段
检测到线程已经终止执行;

8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

我们来详细看看上面每条规则(摘自《深入理解Java虚拟机第12章》):
程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。
锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。
传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。
上面八条是原生Java满足Happens-before关系的规则,但是我们可以对他们进行推导出其他满足happens-before的规则:

private int i = 0;
 
public void write(int j ){
    i = j;
}
 
public int read(){
    return i;
}

我们约定线程A执行write(),线程B执行read(),且线程A优先于线程B执行,那么线程B获得结果是什么?;我们就这段简单的代码一次分析happens-before的规则(规则5、6、7、8 + 推导的6条可以忽略,因为他们和这段代码毫无关系):由于两个方法是由不同的线程调用,所以肯定不满足程序次序规则;两个方法都没有使用锁,所以不满足锁定规则;变量i不是用volatile修饰的,所以volatile变量规则不满足;传递规则肯定不满足;所以我们无法通过happens-before原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B指定,但是就是无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。那么怎么修复这段代码呢?满足规则2、3任一即可。
happens-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的主要依据,保证了多线程环境下的可见性。

JIT指令重排序

不知道大家有没有感觉到在平时的开发过程中以及系统运行过程中,系统刚开始运行的时候性能不如后面的好,简单来说就是你重复运行一段代码比如for while等代码块的时候,性能越来越好。如果没有这种感觉证明你对JVM了解的太少,我们的JVM解释器有两种,模板解释器和字节码解释器,字节码解释器是需要将java字节码解释成C++代码,然后在生成硬编码(汇编),而模板解释器是直接将java字节码通过模板解释器解释成硬编码;而我们的JTI即时编译在什么情况下触发呢?我这边有个疑问,JTI即时编译是否就是通过模板解释器直接编译成了汇编代码,这里就涉及到了热点代码以及热点代码缓冲区,热点代码针对的是可以是方法,可以是代码块,java查看热点代码缓冲区的大小可以根据下面的命令:

java -XX:+PrintFlagsFinal -version|grep CompileThreshold
     intx CompileThreshold                          = 10000           {pd product}
    uintx IncreaseFirstTierCompileThresholdAt       = 50              {product}
     intx Tier2CompileThreshold                     = 0               {product}
     intx Tier3CompileThreshold                     = 2000            {product}
     intx Tier4CompileThreshold                     = 15000           {product}
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)

CompileThreshold 就是热点代码的缓冲区大小,如果一个代码块for或者while,jvm不可能每次都去编译执行,当执行达到了热点代码的阈值过后,就回将热点代码缓存起来,下次执行就直接使用汇编代码去执行,所以为什么在一些热冷备的系统中,突然切换到冷机过后,如果这个时候并发上来了,那么有可能就会出现系统直接挂掉,因为这个时候热点代码缓冲区还没有,就全部采用重新编译执行,我们来看我们编写的代码的编译过程:
在这里插入图片描述
基本上是按照这个流程编译执行的,但是我们来思考我们的编译器在那个过程可能会出现指令重排序,首先1-2的过程肯定不会出现指令重排序的情况,因为如果1-2发生了指令重排序,那么我们的反编译就没有办法做了,反编译的时候那个知道你的代码顺序;其实JIT就是在2-3产生的,热点代码也是在这里产生的,JTI产生的汇编指令代码的时候,当触发了热点代码,就会放入热点代码缓冲区,下次执行就直接读取指令进行执行,那么指令重排序也就发生在这里,我们在前面说过,指令重排序在单线程环境下,不能影响最终的执行结果,也就是要遵守as-if-serial,而在多线程环境下,也要遵守happens-before原则,也就是要保证线程的数据安全。
总结下前面:

1. 指令重排序是编译器优化的结果;
2. 在单线程环境下指令重排序不能影响最终的执行结果,也就是遵循as-if-serial;在多线程环境下,遵守happens-before原则,保证线程的数据安全;
3. JIT即时编译器在编译成汇编指令时可以进行指令重排序
4. CPU执行时乱序的,为什么乱序,比如CPU在执行某一条指令的时候,发现该数据失效了,需要重新加载,那么这个时候是不会等着的,会去先执行其他指令,也就是乱序,乱序的特性一定会提升性能的,所以乱序也可以在一定的程度上称为指令重排序。

指令重排序的手段

在前面已经很详细的说了指令重排序和内存屏障,其实内存屏障就是让所有的线程可见;而指令重排序是能提升我们的程序的执行性能的,所有在合适的条件下指令重排序是允许并且是很有必要的,但是有些情况就要禁止指令重排序的,比如我们上面说的懒汉单例模式,不知道大家在看JDK底层源码的实现的时候有没有看到相关指令重排序的代码,我这里分享下我自己看到的:

public LinkedBlockingQueue(Collection c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock putLock = this.putLock;
    putLock.lock(); // Never contended, but necessary for visibility
    try {
        int n = 0;
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            if (n == capacity)
                throw new IllegalStateException("Queue full");
            enqueue(new Node(e));
            ++n;
        }
        count.set(n);
    } finally {
        putLock.unlock();
    }
}

上面的代码是LinkedBlockingQueue的构造方法,上面的就用到了禁止指令重排序的功能我们看第四行代码的解释,Never contended, but necessary for visibility(从未竞争过,但对可见性是必要的) Doug Lea对这句话的解释很明显了,这个加的锁毫无用处,简简单单的就是开启了内存屏障,进而禁止指令重排序,因为 this(Integer.MAX_VALUE);这行代码可能只需过长导致没有初始化完成,进而程序出现问题;我们前面已经说过volatile关键字其实就是生成了汇编指令lock,告诉cpu我这个变量启用了缓存一致性协议,也就是说对于X86的机器来说,内存屏障启用是根据lock指令来实现的,lock指令告诉CPU启用缓存一致性协议。
我们来看下一段代码指令重排序的例子:

public class T0918 {
    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;
        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            /**
             *  x,y:
             */
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortWait(20000);
                    a = 1;
                    x = b;
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            });

            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();

            System.out.println("第" + i + "次(" + x + "," + y + ")");

            if (x==0&&y==0){
                break;
            }

        }

    }

    public static void shortWait(long interval){
        long start = System.nanoTime();
        long end;
        do{
            end = System.nanoTime();
        }while(start + interval >= end);
    }
}

上面代码的执行结果可能出现哪几种情况呢?x=?y=?上面的程序会启动两个线程,对x和y进行赋值,会出现哪几种情况呢?1,1, 0,1 会不会出现0,0呢?因为上面没有禁止指令重排序和内存屏障,那么0,0的可能也会出现的,因为是乱序的,所以执行结果如下:
在这里插入图片描述
那么我们手动编写一个开启内存屏障的代码看下

public class UnSafeFactory {


    public static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class T0918 {
    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;
        while (true) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            /**
             *  x,y:
             */
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    shortWait(20000);
                    a = 1;
                    UnSafeFactory.getUnsafe().storeFence();
                    x = b;
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    UnSafeFactory.getUnsafe().storeFence();
                    y = a;
                }
            });

            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();

            System.out.println("第" + i + "次(" + x + "," + y + ")");

            if (x==0&&y==0){
                break;
            }

        }

    }

    public static void shortWait(long interval){
        long start = System.nanoTime();
        long end;
        do{
            end = System.nanoTime();
        }while(start + interval >= end);
    }
}

UnSafeFactory.getUnsafe().storeFence();开启了内存屏障所以执行了很久还是:
在这里插入图片描述
一直在执行,没有停下来但是我们还是不建议大家在代码中使用Unsafe类,比较危险,因为这个类直接操作了内存,在很多大厂的代码编程规约中,是不允许直接使用UnSafe这个类的,当然了除了使用这个类,加volatile关键字一样的可以开启内存屏障,禁止指令重排序

private static volatile int x = 0, y = 0;

private static  volatile  int a = 0, b = 0;

代码已经执行了10分钟了,还是在运行如:
在这里插入图片描述
所以使用volatitle一样可以开启内存屏障和禁止指令重排序。

volatile重排序规则

在这里插入图片描述
结论:

  1. 第二个操作是volatile写,不管第一个操作是什么都不会重排序
  2. 第一个操作是volatile读,不管第二个操作是什么都不会重排序
  3. 第一个操作是volatile写,第二个操作是volatile读,也不会发生重排序

JMM内存屏障插入策略:
5. 在每个volatile写操作的前面插入一个StoreStore屏障
2. 在每个volatile写操作的后面插入一个StoreLoad屏障
3. 在每个volatile读操作的后面插入一个LoadLoad屏障
4. 在每个volatile读操作的后面插入一个LoadStore屏障
5.
注意:X86处理器不会对读-读、读-写和写-写操作做重排序, 会省略掉这3种操作类型对应的内存屏障。仅会对写-读操作做重排序,所以volatile写-读操作只需要在volatile写后插入StoreLoad屏障

在这里插入图片描述
在这里插入图片描述
上面的代码是openjdk8的内存屏障的实现,看到上面的lock指令了吗?没错,jdk底层就是通过lock指令实现的内存屏障总结下:

  1. volatile可以禁止指令重排序
  2. volatile可以通过汇编lock指令开启mfence(内存屏障)
  3. 多线程中volatile保证了内存的可见性和有序性(禁止指令重排序)
内存屏障与volatile(C语言版) 内存屏障与volatile(C语言版) 最有价值的写在最前面 内存屏障与volatile是高并发编程中比较常用的两个技术,无锁队列的时候就会用到这两项技术。然而这两项技术设计比较广的基础知识,所以比较难以理解,也比较不容易解释清楚。关于内存屏障和volatile网上有相当多的资料,但是总感觉还是不够系统和深入。当然由于我自身水平有限,所以也不敢保证就能把这两个概念说清楚。所以在文章的开始,先列举一些我在学习过程中比较好的资料。 1.基本概念 https://blog.youkuaiyun.com/legend050 阅读详情

相关推荐

volatile内存语义、原理详解、内存屏障

volatile

没头脑遇到不高兴 1万+

什么是指令重排

指令重排(Instruction Reordering)是计算机编译器和处理器在执行程序时对指令顺序进行重新排序的优化技术。它的目的是提高程序的性能和并行度,但可能会导致意想不到的结果,特别是在多线程环境下。

Li2992673708的博客 1862

Volatile 以DCL失效谈内存屏障用来禁止指令重排序的原理

引言 大家都知道volatile关键字具有两重语义即: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 2)禁止进行指令重排序。 第一个好理解,也就是说每次修改都立即更新到主内存,那么禁止重排序这个在网上更多的解释是说使用了内存屏障,使得前后的指令无法进行重排序。(关于volatile详解) 那么问题来了,什么是内存屏障?...

HJsir的博客 2591

聊聊指令重排序

java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的**执行顺序可以与代码顺序不一致,此过程叫指令的重排序。**

qq_25409421的博客 3万+

volatile指令重排案例分析

volatile指令重排案例分析 大厂面试题: 1、请你谈谈对volatile的理解? 2、CAS你知道吗? 3、原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗? 4、我们都知道ArrayList是线程不安全的,请编码写一个不安全的案例并给出解决方案? 5、公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁。 6、CountDownLatc...

泷泷养的乔小胖 4433

volatile,指令重排和内存屏障

volatile我们已经再熟悉不过了,他保证了属性的可见性并且禁止指令重排 对于可见性:也就是一个线程改了值,另外一个线程可以立马看到结果,也就是但操作是原子的 对于指令重排,有两种:一种是编译重排,一种是CPU重排(运行期重排),编译重排这里的编译不是变成橙字节码,而是JVM解释成字节码的时候的重排, 那么这两种重排都会有一些保证:编译器重排有一些happenbefore规则,在不违反这些原则的基础上进行重排,而有一些指令他是在编译期间就能知道顺序的,比如说new Object()操作包含以下几步:

dw147258dw的专栏 1028

指令重排以及案例

借助 java 并发压测工具 jcstress https://wiki.openjdk.java.net/display/CodeTools/jcstress。JVM 会在不影响正确性的前提下,可以调整语句的执行顺序,思考下面一段代码。可以看到上面的结果, 指令重排出现的概率很低, 我是没试出来。比如 : 调整为 下面的 , 最终的结果也不会发生变化。在不改变程序结果的前提下,这些指令的各个阶段可以通过。指令重排的前提是,重排指令不能影响结果,例如。生成对应的项目后, 我们就可以添加相应的代码。...

兀坐晴窗独饮茶 4548

基于OFDM的实时图像传输系统MATLAB仿真:MPSK与MQAM调制性能对比与误码率分析

本文详细介绍了基于OFDM的实时图像传输系统在MATLAB中的仿真实现,重点对比了MPSK与MQAM调制方式的性能差异与误码率分析。通过搭建OFDM系统模型,探讨了QPSK和16QAM在不同信道条件下的表现,包括频谱效率、抗噪能力和误码率等关键指标。文章还提供了MATLAB代码示例和实用技巧,帮助读者快速掌握OFDM图像传输系统的核心算法与优化方法。

weixin_29064009的博客 355

jvm(三)指令重排 & 内存屏障 & 可见性 & volatile & happen before

参考文档: https://tech.meituan.com/java-memory-reordering.html http://0xffffff.org/2017/02/21/40-atomic-variable-mutex-and-memory-barrier/ 内存可见性:http://blog.youkuaiyun.com/ty_laurel/article/details/52403718 ...

weixin_30906425的博客 418

Halcon 简单的形状匹配定位与区域跟随

【代码】Halcon简单的形状匹配定位与区域跟随。

唐大狗 2806

固件测试工具选型需要考察的功能点汇总

通过专业的固件测试工具可以帮助我们提高测试效率、提升测试的覆盖度、确保测试的准确性,保障产品的安全和质量。本文我们讲固件测试工具的功能点进行梳理和汇总,方便大家在采购固件测试工具时进行选型比对。

daopuyun的博客 2112

Java的volatile、final语义中的内存屏障与指令重排

Java的volatile、final语义中的内存屏障与指令重排

weixin_45244050的博客 1586

3D缺陷检测实战:从点云处理到工业质检优化

本文深入探讨了3D缺陷检测在工业质检中的实战应用,详细阐述了从点云处理到深度学习模型部署的完整技术链路。文章通过PCB焊点检测等实际案例,分析了3D视觉相比2D检测在识别深度、形状等缺陷方面的优势,并分享了点云滤波、分割、特征提取以及利用YOLOv5等模型进行高效检测的优化心得与落地挑战。

weixin_28358083的博客 338

Volatile指令重排详解

一、Volatile不可见性 Volatile关键 二、CPU的乱序执行 CPU在进行读等待的同时执行指令,是CPU乱序的根源,不是乱,而是提高效率 三、对象的创建过程 对象的创建过程: 创建-&gt;初始化-&gt;建立连接 1.先申请内存,赋值默认值0 2.构造方法赋值初始值,8 3.建立连接,t-&gt;T 四、DCL单例 4.1 DCL单例代码 DCL单例模式 public class Singleton { pr...

Jak的博客 6100

MediaPipe 教程:从安装到应用-全网最全

MediaPipe是Google推出的开源框架,专为构建机器学习管道设计,可以高效处理视频、音频等时间序列数据。本文教程从安装到应用逐步讲解: 安装步骤简单,只需通过pip安装mediapipe包并验证版本; 重点演示人体姿态检测功能,可识别33个身体关键点; 提供基础代码实现,包括视频捕获、关键点检测与可视化; 强调模块化开发,将功能封装成类以提高代码复用性。 教程适合初学者,通过清晰指引和实用代码示例,帮助快速实现计算机视觉应用。

FJN110的博客 1563

Apifox接口测试+自动化测试

Apifox 接口测试+自动化测试快速入门!

程序员小杨哥的博客 1万+

Volatile禁止指令重排

Volatile禁止指令重排 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种: 源代码 -&gt; 编译器优化的重排 -&gt; 指令并行的重排 -&gt; 内存系统的重排 -&gt; 最终执行指令 单线程环境里面确保最终执行结果和代码顺序的结果一致 处理器在进行重排序时,必须要考虑指令之间的数据依赖性 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。 指令重排 - example 1 public vo

Hai-Yang-code的博客 6188

在 SAP Gateway 主机上把 SAML 2.0 配置为 Service Provider:从架构到落地的完整实战

本文详细介绍了在SAP Gateway主机上配置SAML 2.0作为Service Provider的全流程。从概念梳理到实际操作,涵盖了关键步骤:启用本地SP服务、通过反向代理导出元数据、在IdP侧完成对接、配置信任关系与Artifact绑定、设置NameID身份联合等核心环节。特别强调了HTTP Artifact绑定的背道通信机制和证书配置要点,并针对常见错误提供了排障指引。文章注重实操性,结合SAP官方文档与社区最佳实践,为运维团队提供了一套完整的SAML SSO实施框架。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 151

什么是指令重排序和内存屏障,看完你就懂了

面试官在问到多线程编程的时候,指令重排序、内存屏障经常会被提起。如果你对这两者有一定的理解,那这就是你的加分项。

吴名氏的博客 14万+

Python 爬虫实战:爬取股票数据做 K 线图可视化

摘要:本文介绍基于Python的股票数据爬取与K线图可视化全流程技术方案。通过Requests库抓取东方财富网股票数据,使用Pandas进行清洗处理,并利用mplfinance库实现专业K线图绘制。文章详细讲解了数据爬取接口分析、反爬策略配置、K线图绘制方法(包含基础K线图和带均线的进阶版本),以及核心统计指标计算。该方案可应用于股票价格趋势分析,为投资决策提供数据支持,同时强调了数据使用的合规性和技术优化方向。

2503_91057718的博客 2049

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain&#39;s apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = &quot;R. B. Palm&quot;, title = &quot;Prediction as a candidate for learning deep hierarchical models of data&quot;, year = &quot;2012&quot;,}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath(&#39;DeepLearnToolbox&#39;));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand(&#39;state&#39;,0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W&#39;); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand(&#39;state&#39;,0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = &#39;sigm&#39;;%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.10, &#39;Too big error&#39;);```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand(&#39;state&#39;,0)sae = saesetup([784 100]);sae.ae{1}.activation_function = &#39;sigm&#39;;sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)&#39;)% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;;nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.16, &#39;Too big error&#39;);```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x&#39;,28,28,60000))/255;test_x = double(reshape(test_x&#39;,28,28,10000))/255;train_y = double(train_y&#39;);test_y = double(test_y&#39;);%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you&#39;ll get around 1.2% errorrand(&#39;state&#39;,0)cnn.layers = { struct(&#39;type&#39;, &#39;i&#39;) %input layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 6, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %sub sampling layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 12, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er&lt;0.12, &#39;Too big error&#39;);```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.08, &#39;Too big error&#39;);%% ex2 neural net with L2 weight decayrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.1, &#39;Too big error&#39;);%% ex3 neural net with dropoutrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.1, &#39;Too big error&#39;);%% ex4 neural net with sigmoid activation functionrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.1, &#39;Too big error&#39;);%% ex5 plotting functionalityrand(&#39;state&#39;,0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = &#39;softmax&#39;; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.1, &#39;Too big error&#39;);%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand(&#39;state&#39;,0)nn = nnsetup([784 20 10]); nn.output = &#39;softmax&#39;; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er &lt; 0.1, &#39;Too big error&#39;);```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free &quot;Bitdeli Badge&quot;)

什么是 volatile 和指令重排?

在 Java 中, 是一个关键字,用于修饰变量。它有以下两个主要作用:保证可见性:当一个线程修改了 变量时,其他线程能够立即看到这个修改。防止指令重排:保证 变量的操作不会被线程内的指令重排优化。指令重排是指在程序执行时,编译器、运行时环境或 CPU 可能会调整指令的执行顺序,以提高性能。例如: 在某些情况下,指令2可能会被提前执行,变成: 这种调整通常是安全的,但在多线程环境下,可能会导致问题。为了防止指令重排,Java 在 变量操作前后插入了内存屏障(Memory Barrier)。内存屏

weixin_55358075的博客 1559

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

volatile禁止重排序的原理-内存屏障

目录JMM中volitile内存语义的实现volitile写的内存屏障volitile读的内存屏障内存屏障的优化内存屏障手动添加 程序中,为了更高效的执行,会对代码或者指令进行重排序,在java中程序运行过程中可能会发生以下情况的重排序: 编译器优化重排序; 指令级并行重排序; 内存系统重排序; 为了实现volatile内存语义,JMM会分别限制这两种类型的重排序类型。 下图是JMM针对编译器制定的volatile重排序规则表。 举例来说: 第三行最后一个单元格的意思是:在程序中,当第一个操作为普通

binga的博客 4464

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

内存屏障&指令重排序

这里写目录标题什么是内存屏障内存屏障与处理器重排序内存屏障与volatile 什么是内存屏障 内存屏障它是一条CPU指令: 确保一些特定操作执行的顺序; 影响一些数据的可见性(可能是某些指令执行后的结果)。 内存屏障与处理器重排序 现代的处理器使用写缓冲区来临时保存向内存写入的数据,每个处理器都有自己的缓冲区。写缓冲区可以保证指令流水线持续运行,它可以避免由于处理器停顿下来等待向内存写入数据产生的阻塞。同时,通过以批处理的方式刷新写缓冲区,以及合并写缓冲区对同一内存地址的多次写,可以减少对内存总线的占

summer_fish的专栏 1087

关于指令重排内存屏障和总线风暴

指令重排 java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。 从源码到最后执行的指令序列过程是: 源码-&gt;编译器优化重排序-&gt;指令级并行重排序-&gt;内存系统重排序-&gt;最后执行的指令序列 内存屏障 内存屏障可以禁止指令重排序 从上图可以看出: 1)当第二个操作是vola...

li12127878的博客 6945

指令重排序、内存屏障很难?看完这篇你就懂了

面试官在问到多线程编程的时候,指令重排序、内存屏障经常会被提起。如果你对这两者有一定的理解,那这就是你的加分项。

Java鱼仔的博客 1806

volatile原理(内存屏障)

volatile 场景 一个线程写,其他线程读的情况 double-check-lock时,synchronized同步代码块外共享变量的指令重排序问题 同步机制 volatile 是 Java 虚拟机提供的轻量级的同步机制(三大特性) 保证可见性 不保证原子性 保证有序性(禁止指令重排) 性能:volatile 修饰的变量进行读操作与普通变量几乎没什么差别,但是写操作相对慢一些,因为需要在本地代码中插入很多内存屏障来保证指令不会发生乱序执行,但是开

2301_79437276的博客 2332
上一篇: 并发编程之JMM模型&Volatile底层原理
下一篇: Synchronized详细介绍之锁升级过程
Life  journey
博客等级 码龄17年 1万+粉丝 80原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值