JVM内存模型、指令重排、内存屏障概念解析

多线程安全——volatile解析 概念 Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。 特性 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程 阅读详情

 在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器、运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要。否则,你很难搞清楚哪些操作是在并发先绝对安全的?哪些是相对安全的?哪些并发同步手段性能最低?valotile的二层语义分别是什么?等等。

     原文地址:http://tech.meituan.com/java-memory-reordering.html

一、什么是重排序

请先看这样一段代码

复制代码
 1 public class PossibleReordering {
 2 static int x = 0, y = 0;
 3 static int a = 0, b = 0;
 4 
 5 public static void main(String[] args) throws InterruptedException {
 6     Thread one = new Thread(new Runnable() {
 7         public void run() {
 8             a = 1;
 9             x = b;
10         }
11     });
12 
13     Thread other = new Thread(new Runnable() {
14         public void run() {
15             b = 1;
16             y = a;
17         }
18     });
19     one.start();other.start();
20     one.join();other.join();
21     System.out.println(“(” + x + “,” + y + “)”);
22 }
复制代码

      很容易想到这段代码的运行结果可能为(1,0)、(0,1)或(1,1),因为线程one可以在线程two开始之前就执行完了,也有可能反之,甚至有可能二者的指令是同时或交替执行的。

      然而,这段代码的执行结果也可能是(0,0). 因为,在实际运行时,代码指令可能并不是严格按照代码语句顺序执行的。得到(0,0)结果的语句执行过程,如下图所示。值得注意的是,a=1和x=b这两个语句的赋值操作的顺序被颠倒了,或者说,发生了指令“重排序”(reordering)。(事实上,输出了这一结果,并不代表一定发生了指令重排序,内存可见性问题也会导致这样的输出,详见后文)

   

     对重排序现象不太了解的开发者可能会对这种现象感到吃惊,但是,笔者开发环境下做的一个小实验证实了这一结果。

     实验代码是构造一个循环,反复执行上面的实例代码,直到出现a=0且b=0的输出为止。实验结果说明,循环执行到第13830次时输出了(0,0)。

     大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待3。通过乱序执行的技术,处理器可以大大提高执行效率。
     除了处理器,常见的Java运行时环境的JIT编译器也会做指令重排序操作,即生成的机器指令与字节码指令顺序不一致。

二、as-if-serial语义

     As-if-serial语义的意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
     比如,为了保证这一语义,重排序不会发生在有数据依赖的操作之中。

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

      将上面的代码编译成Java字节码或生成机器指令,可视为展开成了以下几步动作(实际可能会省略或添加某些步骤)。

  1. 对a赋值1
  2. 对b赋值2
  3. 取a的值
  4. 取b的值
  5. 将取到两个值相加后存入c

      在上面5个动作中,动作1可能会和动作2、4重排序,动作2可能会和动作1、3重排序,动作3可能会和动作2、4重排序,动作4可能会和1、3重排序。但动作1和动作3、5不能重排序。动作2和动作4、5不能重排序。因为它们之间存在数据依赖关系,一旦重排,as-if-serial语义便无法保证。

      为保证as-if-serial语义,Java异常处理机制也会为重排序做一些特殊处理。例如在下面的代码中,y = 0 / 0可能会被重排序在x = 2之前执行,为了保证最终不致于输出x = 1的错误结果,JIT在重排序时会在catch语句中插入错误代偿代码,将x赋值为2,将程序恢复到发生异常时应有的状态。这种做法的确将异常捕捉的逻辑变得复杂了,但是JIT的优化的原则是,尽力优化正常运行下的代码逻辑,哪怕以catch块逻辑变得复杂为代价,毕竟,进入catch块内是一种“异常”情况的表现。

复制代码
 1 public class Reordering {
 2     public static void main(String[] args) {
 3         int x, y;
 4         x = 1;
 5         try {
 6             x = 2;
 7             y = 0 / 0;    
 8         } catch (Exception e) {
 9         } finally {
10             System.out.println("x = " + x);
11         }
12     }
13 }
复制代码

三、内存访问重排序与内存可见性

      计算机系统中,为了尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓存(cache)以提高性能。其模型如下图所示。

     在这种模型下会存在一个现象,即缓存中的数据与主内存的数据并不是实时同步的,各CPU(或CPU核心)间缓存的数据也不是实时同步的。这导致在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。从程序的视角来看,就是在同一个时间点,各个线程所看到的共享变量的值可能是不一致的。
     有的观点会将这种现象也视为重排序的一种,命名为“内存系统重排序”。因为这种内存可见性问题造成的结果就好像是内存访问指令发生了重排序一样。
     这种内存可见性问题也会导致章节一中示例代码即便在没有发生指令重排序的情况下的执行结果也还是(0, 0)。

四、内存访问重排序与Java内存模型

      Java的目标是成为一门平台无关性的语言,即Write once, run anywhere. 但是不同硬件环境下指令重排序的规则不尽相同。例如,x86下运行正常的Java程序在IA64下就可能得到非预期的运行结果。为此,JSR-1337制定了Java内存模型(Java Memory Model, JMM),旨在提供一个统一的可参考的规范,屏蔽平台差异性。从Java 5开始,Java内存模型成为Java语言规范的一部分。
     根据Java内存模型中的规定,可以总结出以下几条happens-before规则。Happens-before的前后两个操作不会被重排序且后者对前者的内存可见。

  • 程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
  • 监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
  • volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
  • 线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。
  • 线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。
  • 中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
  • 终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
  • 传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于C

      Happens-before关系只是对Java内存模型的一种近似性的描述,它并不够严谨,但便于日常程序开发参考使用,关于更严谨的Java内存模型的定义和描述,请阅读JSR-133原文或Java语言规范章节17.4。

      除此之外,Java内存模型对volatile和final的语义做了扩展。对volatile语义的扩展保证了volatile变量在一些情况下不会重排序,volatile的64位变量double和long的读取和赋值操作都是原子的。对final语义的扩展保证一个对象的构建方法结束前,所有final成员变量都必须完成初始化(的前提是没有this引用溢出)。

      Java内存模型关于重排序的规定,总结后如下表所示。

      表中“第二项操作”的含义是指,第一项操作之后的所有指定操作。如,普通读不能与其之后的所有volatile写重排序。另外,JMM也规定了上述volatile和同步块的规则尽适用于存在多线程访问的情景。例如,若编译器(这里的编译器也包括JIT,下同)证明了一个volatile变量只能被单线程访问,那么就可能会把它做为普通变量来处理。
      留白的单元格代表允许在不违反Java基本语义的情况下重排序。例如,编译器不会对对同一内存地址的读和写操作重排序,但是允许对不同地址的读和写操作重排序。

      除此之外,为了保证final的新增语义。JSR-133对于final变量的重排序也做了限制。

    • 构建方法内部的final成员变量的存储,并且,假如final成员变量本身是一个引用的话,这个final成员变量可以引用到的一切存储操作,都不能与构建方法外的将当期构建对象赋值于多线程共享变量的存储操作重排序。例如对于如下语句
      x.finalField = v; ... ;构建方法边界sharedRef = x;
      v.afield = 1; x.finalField = v; ... ; 构建方法边界sharedRef = x;
      这两条语句中,构建方法边界前后的指令都不能重排序。
    • 初始读取共享对象与初始读取该共享对象的final成员变量之间不能重排序。例如对于如下语句
      x = sharedRef; ... ; i = x.finalField;
      前后两句语句之间不会发生重排序。由于这两句语句有数据依赖关系,编译器本身就不会对它们重排序,但确实有一些处理器会对这种情况重排序,因此特别制定了这一规则。

五、内存屏障

      内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
      内存屏障可以被分为以下几种类型
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。        在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

       有的处理器的重排序规则较严,无需内存屏障也能很好的工作,Java编译器会在这种情况下不放置内存屏障。
       为了实现上一章中讨论的JSR-133的规定,Java编译器会这样使用内存屏障。

     为了保证final字段的特殊语义,也会在下面的语句加入内存屏障。
     x.finalField = v; StoreStore; sharedRef = x;

六、Intel 64/IA-32架构下的内存访问重排序

     Intel 64和IA-32是我们较常用的硬件环境,相对于其它处理器而言,它们拥有一种较严格的重排序规则。Pentium 4以后的Intel 64或IA-32处理的重排序规则如下。9

在单CPU系统中

  • 读操作不与其它读操作重排序。
  • 写操作不与其之前的写操作重排序。
  • 写内存操作不与其它写操作重排序,但有以下几种例外
  • CLFLUSH的写操作
  • 带有non-temporal move指令(MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, and MOVNTPD)的streaming写入。
  • 字符串操作
  • 读操作可能会与其之前的写不同位置的写操作重排序,但不与其之前的写相同位置的写操作重排序。
  • 读和写操作不与I/O指令,带锁的指令或序列化指令重排序。
  • 读操作不能重排序到LFENCE和MFENCE之前。
  • 写操作不能重排序到LFENCE、SFENCE和MFENCE之前。
  • LFENCE不能重排序到读操作之前。
  • SFENCE不能重排序到写之前。
  • MFENCE不能重排序到读或写操作之前。

在多处理器系统中

  • 各自处理器内部遵循单处理器的重排序规则。
  • 单处理器的写操作对所有处理器可见是同时的。
  • 各自处理器的写操作不会重排序。
  • 内存重排序遵守因果性(causality)(内存重排序遵守传递可见性)。
  • 任何写操作对于执行这些写操作的处理器之外的处理器来看都是一致的。
  • 带锁指令是顺序执行的。

     值得注意的是,对于Java编译器而言,Intel 64/IA-32架构下处理器不需要LoadLoad、LoadStore、StoreStore屏障,因为不会发生需要这三种屏障的重排序。

七、一例Intel 64/IA-32架构下的代码性能优化

      现在有这样一个场景,一个容器可以放一个东西,容器支持create方法来创建一个新的东西并放到容器里,支持get方法取到这个容器里的东西。我们可以较容易地写出下面的代码。

复制代码
 1 public class Container {
 2     public static class SomeThing {
 3         private int status;
 4 
 5         public SomeThing() {
 6             status = 1;
 7         }
 8 
 9         public int getStatus() {
10             return status;
11         }
12     }
13 
14     private SomeThing object;
15 
16     public void create() {
17         object = new SomeThing();
18     }
19 
20     public SomeThing get() {
21         while (object == null) {
22             Thread.yield(); //不加这句话可能会在此出现无限循环
23         }
24         return object;
25     }
26 }
复制代码

      在单线程场景下,这段代码执行起来是没有问题的。但是在多线程并发场景下,由不同的线程create和get东西,这段代码是有问题的。问题的原因与普通的双重检查锁定单例模式(Double Checked Locking, DCL)10类似,即SomeThing的构建与将指向构建中的SomeThing引用赋值到object变量这两者可能会发生重排序。导致get中返回一个正被构建中的不完整的SomeThing对象实例。为了解决这一问题,通常的办法是使用volatile修饰object字段。这种方法避免了重排序,保证了内存可见性,摒弃比使用同步块导致的性能损失更小。但是,假如使用场景对object的内存可见性并不敏感的话(不要求一个线程写入了object,object的新值立即对下一个读取的线程可见),在Intel 64/IA-32环境下,有更好的解决方案。

     根据上一章的内容,我们知道Intel 64/IA-32下写操作之间不会发生重排序,即在处理器中,构建SomeThing对象与赋值到object这两个操作之间的顺序性是可以保证的。这样看起来,仅仅使用volatile来避免重排序是多此一举的。但是,Java编译器却可能生成重排序后的指令。但令人高兴的是,Oracle的JDK中提供了Unsafe. putOrderedObject,Unsafe. putOrderedInt,Unsafe. putOrderedLong这三个方法,JDK会在执行这三个方法时插入StoreStore内存屏障,避免发生写操作重排序。而在Intel 64/IA-32架构下,StoreStore屏障并不需要,Java编译器会将StoreStore屏障去除。比起写入volatile变量之后执行StoreLoad屏障的巨大开销,采用这种方法除了避免重排序而带来的性能损失以外,不会带来其它的性能开销。
     我们将做一个小实验来比较二者的性能差异。一种是使用volatile修饰object成员变量。

复制代码
 1 public class Container {
 2     public static class SomeThing {
 3         private int status;
 4 
 5         public SomeThing() {
 6             status = 1;
 7         }
 8 
 9         public int getStatus() {
10             return status;
11         }
12     }
13 
14     private volatile  SomeThing object;
15 
16     public void create() {
17         object = new SomeThing();
18     }
19 
20     public SomeThing get() {
21         while (object == null) {
22             Thread.yield(); //不加这句话可能会在此出现无限循环
23         }
24         return object;
25     }
26 }
复制代码

     一种是利用Unsafe. putOrderedObject在避免在适当的位置发生重排序。

复制代码
 1 public class Container {
 2     public static class SomeThing {
 3         private int status;
 4 
 5         public SomeThing() {
 6             status = 1;
 7         }
 8 
 9         public int getStatus() {
10             return status;
11         }
12     }
13 
14     private SomeThing object;
15 
16     private Object value;
17     private static final Unsafe unsafe = getUnsafe();
18     private static final long valueOffset;
19     static {
20         try {
21             valueOffset = unsafe.objectFieldOffset(Container.class.getDeclaredField("value"));
22         } catch (Exception ex) { throw new Error(ex); }
23     }
24 
25     public void create() {
26         SomeThing temp = new SomeThing();
27         unsafe.putOrderedObject(this, valueOffset, null);    //将value赋null值只是一项无用操作,实际利用的是这条语句的内存屏障
28         object = temp;
29     }
30 
31     public SomeThing get() {
32         while (object == null) {
33             Thread.yield();
34         }
35         return object;
36     }
37 
38 
39     public static Unsafe getUnsafe() {
40         try {
41             Field f = Unsafe.class.getDeclaredField("theUnsafe");
42             f.setAccessible(true);
43             return (Unsafe)f.get(null);
44         } catch (Exception e) {
45         }
46         return null;
47     }
48 }
复制代码

     由于直接调用Unsafe.getUnsafe()需要配置JRE获取较高权限,我们利用反射获取Unsafe中的theUnsafe来取得Unsafe的可用实例。
     unsafe.putOrderedObject(this, valueOffset, null)
     这句仅仅是为了借用这句话功能的防止写重排序,除此之外无其它作用。

     利用下面的代码分别测试两种方案的实际运行时间。在运行时开启-server和 -XX:CompileThreshold=1以模拟生产环境下长时间运行后的JIT优化效果。

复制代码
 1 public static void main(String[] args) throws InterruptedException {
 2     final int THREADS_COUNT = 20;
 3     final int LOOP_COUNT = 100000;
 4 
 5     long sum = 0;
 6     long min = Integer.MAX_VALUE;
 7     long max = 0;
 8     for(int n = 0;n <= 100;n++) {
 9         final Container basket = new Container();
10         List<Thread> putThreads = new ArrayList<Thread>();
11         List<Thread> takeThreads = new ArrayList<Thread>();
12         for (int i = 0; i < THREADS_COUNT; i++) {
13             putThreads.add(new Thread() {
14                 @Override
15                 public void run() {
16                     for (int j = 0; j < LOOP_COUNT; j++) {
17                         basket.create();
18                     }
19                 }
20             });
21             takeThreads.add(new Thread() {
22                 @Override
23                 public void run() {
24                     for (int j = 0; j < LOOP_COUNT; j++) {
25                         basket.get().getStatus();
26                     }
27                 }
28             });
29         }
30         long start = System.nanoTime();
31         for (int i = 0; i < THREADS_COUNT; i++) {
32             takeThreads.get(i).start();
33             putThreads.get(i).start();
34         }
35         for (int i = 0; i < THREADS_COUNT; i++) {
36             takeThreads.get(i).join();
37             putThreads.get(i).join();
38         }
39         long end = System.nanoTime();
40         long period = end - start;
41         if(n == 0) {
42             continue;    //由于JIT的编译,第一次执行需要更多时间,将此时间不计入统计
43         }
44         sum += (period);
45         System.out.println(period);
46         if(period < min) {
47             min = period;
48         }
49         if(period > max) {
50             max = period;
51         }
52     }
53     System.out.println("Average : " + sum / 100);
54     System.out.println("Max : " + max);
55     System.out.println("Min : " + min);
56 }
复制代码

在笔者的计算机上运行测试,采用volatile方案的运行结果如下
Average : 62535770
Max : 82515000
Min : 45161000

采用unsafe.putOrderedObject方案的运行结果如下
Average : 50746230
Max : 68999000
Min : 38038000

      从结果看出,unsafe.putOrderedObject方案比volatile方案平均耗时减少18.9%,最大耗时减少16.4%,最小耗时减少15.8%.另外,即使在其它会发生写写重排序的处理器中,由于StoreStore屏障的性能损耗小于StoreLoad屏障,采用这一方法也是一种可行的方案。但值得再次注意的是,这一方案不是对volatile语义的等价替换,而是在特定场景下做的特殊优化,它仅避免了写写重排序,但不保证内存可见性。

 

###附1 复现重排序现象实验代码

复制代码
 1 public class Test {
 2     private static int x = 0, y = 0;
 3     private static int a = 0, b =0;
 4 
 5     public static void main(String[] args) throws InterruptedException {
 6         int i = 0;
 7         for(;;) {
 8             i++;
 9             x = 0; y = 0;
10             a = 0; b = 0;
11             Thread one = new Thread(new Runnable() {
12                 public void run() {
13                     //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
14                     shortWait(100000);
15                     a = 1;
16                     x = b;
17                 }
18             });
19 
20             Thread other = new Thread(new Runnable() {
21                 public void run() {
22                     b = 1;
23                     y = a;
24                 }
25             });
26             one.start();other.start();
27             one.join();other.join();
28             String result = "第" + i + "次 (" + x + "," + y + ")";
29             if(x == 0 && y == 0) {
30                 System.err.println(result);
31                 break;
32             } else {
33                 System.out.println(result);
34             }
35         }
36     }
37 
38 
39     public static void shortWait(long interval){
40         long start = System.nanoTime();
41         long end;
42         do{
43             end = System.nanoTime();
44         }while(start + interval >= end);
45     }
46 }
复制代码
整合WGCNA、机器学习和孟德尔随机化 (G)通过蛋白质免疫印迹法(Western blot)测定的 UT-B(由 SLC14A1 编码)、ARHGEF38、NEFH、MSMB、KRT23 和 KRT15 在前列腺癌肿瘤组织和癌旁非癌组织中的蛋白质表达水平。本研究利用来自基因表达综合数据库(GEO)的数据集筛选差异表达基因(DEGs),并采用加权基因共表达网络分析(WGCNA),以识别在模块中与前列腺癌高度相关的驱动基因。(F)蓝绿色模块中的模块成员关系(MM)与前列腺癌(PCa)的基因显著性(GS)的散点图。(D)6 个基因之间的相关性分析。 阅读详情

相关推荐

Python+gensim【中文LDA】简洁模型

文章目录完整代码过程详解words_lsdictionarydictionary.doc2bow函数corpuslda 完整代码 from gensim import corpora, models import jieba.posseg as jp # 待分析文本集 text1 = '美国教练坦言,没输给中国女排,是输给了郎平' text2 = '中国女排世界排名第一?真实水平如何,听听巴西和美...

ArYe 2万+

【JAVA 网络编程系列】Netty -- MpscArrayQueue(多生产者单消费者队列)

【JAVA 网络编程系列】Netty -- MpscArrayQueue 【1】伪共享概念简介 【1.1】CPU 缓存架构 越靠近 CPU 的缓存越快也越小,因此 L1 缓存很小但很快,并且紧靠着在使用它的 CPU 内核;L2 大一些,也慢一些,并且仍然只能被一个单独的 CPU 核使用;L3 在现代多核机器中更普遍,仍然更大,更慢,并且被单个插槽上的所有 CPU 核共享;主存保存着程序运行的所有数据,它更大,更慢,由全部插槽上的所有 CPU 核共享; 当 CPU 执行运算的时候,先去 L1 查找所

CopperSun 技术博客 2822

智能优化电池:基于单片机的电池管理系统

设计简介: 本设计是基于单片机的电池管理系统,主要实现以下功能: 可通过LCD1602显示温度、电压电流值; 可通过按键调整温度、电压和电流的阈值; 用ADC0832将模拟量转换为数字量。 标签:51单片机、LCD1602、ADC0832 效果图: 总体资料: 原理图: 软件设计流程: 系统框图: 本设计以STC89C52单片机为核心控制器,加上其他的模块一起组成电动汽车的电池管理的整个系统,...

专注单片机实战教学,分享单片机学习资料 375

Java中的Unsafe

Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。 Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。 Unsafe类是"final"的,不允许继承。且构造函数是...

weixin_34228662的博客 287

H3C VRF实验

POP-vpn-instance-RT1]route-distinguisher 11:11 # VPN实例-RT1的路由区分器值11:11。配置路由泄露,其实就是在一个VPN示例中添加另一个VPN实例的下一跳,比如在VPN实例RT1中添加去往SRV的默认路由。但是也有了一个新的问题,那就是RT1、RT2和RT3也能互访了,这让VPN实例变得没有意义了。[POP]ip vpn-instance RT1 #配置vpn实例-RT1。绑定了VPN实例,所以要把路由添加到VPN实例POP中。

m0_68698993的博客 4023

手把手教你分析解决Innovus DRC Violation 和Antenna Effect Violation

数字IC后端设计培训教程之手把手教你分析解决Innovus DRC Violation 和Antenna Effect Violation

weixin_37584728的博客 2932

Java和C++中的volatile

1.Java中的volatile:在Java内存模型中,线程共享的资源放在主存中,每个线程同时拥有自己的本地内存。而本地内存中存放了被该线程使用到的主内存变量的拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。由此可能导致线程间无法读取变量的最新状态。被volatile修饰的变量在修改时会被强制写到主存中,从而保证该变量对其他线程的可见性。 2.C++中的volat...

DK233333的博客 1035

变色龙哈希函数 Chameleon Hash 可变型区块链

哈希函数 Hash: 众所周知,区块链有着极其优秀的安全性就是因为其充分使用了哈希函数。 哈希简单用一句话来讲,就是:将任意长度输入的字串可转换成一个固定长度的字串,通过原始字串可以很容易地算出转换后的字串,通过转换后的字串很难还原出原始字串。 哈希函数特征: 1. 对于任意m作为输入,得到输出的结果,很难找到另一个输入m'  (m'不等于m),使得m'的Hash结果也为同样的输出

新手村 1万+

【JVM】---重排序

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。 一、重排序介绍 重排序可能会导致多线程程序出现内存可见性问题。(工作内存和主内存,编译器处理器重排序导致的可见性)。 重排序会导致有序性问题,程序的读写顺序和内存的读写顺序不一样(编译器处理器重排序,内存缓冲区(是处理器重排序的内容))。 public class ReorderInMultiThread { int a = 0; boolean flag = true; //写线程 private void

寻梦友的博客 643

vscode+keil嵌入式软件开发全流程

vscode+keil嵌入式软件开发全流程

AspringQ的博客 6780

【NVIDIA-B200 GPU固件升级】NVIDIA Blackwell B200 GPU 固件更新全指南

B200 GPU 固件更新并非 “执行一条命令即可” 的简单操作,而是涉及 “版本兼容性校验、BMC 环境准备、UEFI 引导、Redfish 协议交互” 的系统性工程。从文档翻译与技术解析来看,NVIDIA 的更新流程设计围绕 “安全” 与 “稳定” 两大核心 —— 无论是 0.9 版本的 “跳板限制”,还是 UEFI Shell 的引导要求,本质都是为了避免因操作不当导致硬件损坏。对于数据中心运维人员,建议在更新前做好三项准备:① 确认 GPU 当前固件版本(通过查询),规划升级路径;

jundao1997的专栏 741

图解JVM内存模型、指令重排、内存屏障概念解析

在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器、运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要。否则,你很难搞清楚哪些操作是在并发先绝对安全的?哪些是相对安全的?哪些并发同步手段性能最低?valotile的二层语义分别是什么?等等。 本来打算自己写一篇有关JVM内存模型的博文,后来整理资料的...

M18612259745的博客 258

37、OpenMP:并行编程的强大工具

本文详细介绍了 OpenMP 的核心功能和使用方法,包括执行环境例程、线程同步与协调机制,如关键区域、屏障、原子操作、归约子句、刷新构造和锁机制。此外,还提供了多个实践练习,涵盖矩阵乘法的并行实现、任务池与流水线模式的应用、读写锁的扩展与移植,以及客户端-服务器模式的实现与优化。通过这些内容,读者可以全面掌握 OpenMP 在并行编程中的实际应用与性能优化技巧。

www00的博客 101

JVM内存模型、指令重排、内存屏障概念解析(转载)

在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器、运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要。否则,你很难搞清楚哪些操作是在并发先绝对安全的?哪些是相对安全的?哪些并发同步手段性能最低?valotile的二层语义分别是什么?等等。 本来打算自己写一篇有关JVM内存模型的博文,后来整理资料的时候偶然...

weixin_34190136的博客 101

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

内存模型 类的加载机制 对象创建过程

图解JVM的类加载机制(详细版) https://www.cnblogs.com/chenyangyao/p/5245669.html JVM内存模型、指令重排、内存屏障概念解析 https://www.cnblogs.com/chenyangyao/p/5269622.html 图解JAVA对象的创建过程 https://www.cnblogs.com/chenyan...

Angus 263

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

《Java内存模型与Happens-Before规则:从指令重排序到屏障指令实战》

三大核心问题切入,结合X86架构的MESI协议与JVM源码,深度解析Java内存模型(JMM)的设计哲学。"为什么双重检查锁单例模式必须加volatile?CPU缓存不一致如何导致线程安全问题?不同CPU核心修改会触发缓存行无效化(MESI协议)不同线程间变量传递不可见(除非同步)两个变量位于同一缓存行(64字节)线程操作变量需通过工作内存副本。所有变量存储在主内存。

qq_62112907的博客 440

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: EMQ 百万级开源MQTT消息服务器
下一篇: HashMap在Java1.7与1.8中的区别
大树叶
博客等级 码龄12年 1171粉丝 664原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值