volatile

Java并发机制的底层实现--volatile

Deadly 提交于 2020-03-03 05:59:40
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,JAVA中所使用的并发机制依赖于JVM的实现和CPU的指令。 volatile的应用 在线程并发编程中sychronized和volatile都扮演者重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性“。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。 1.volatile的定义与实现原理 Java语言规范第三版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被唯一和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。 在了解volatile的实现原理之前,我们先来看下与其实现原理相关的CPU术语与说明。 volatile是如何来保证可见性的呢?让我们在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时

volatile 对可见性的保证并不是那么简单

寵の児 提交于 2020-03-02 19:43:56
  数据一致性部分借用大神“耗叔”的博客: https://coolshell.cn/articles/20793.html 。   总结:volatile 关键字通过内存屏障禁止了指令的重排序,并在单个核心中,强制数据的更新及时更新到缓存。在此基础上,依靠多核心处理器的缓存一致性协议等机制,保证了变量的可见性。   在学习 volatile 关键字时总是绕不开两点,保证数据及时更新到内存和禁止指令重排序,基于上述两点 volatile 关键字保证了共享变量在多个线程间的可见性。   虽然说起来知识点不多,但实际上 volatile 的实现是及其复杂的。在 java5 之前 volatile 关键字会经常造成一些无法预料的错误,导致其保守诟病。直到 5 版本对 volatile 进行改进之后才重获新生,官方版本经历的这些波折足以证明其底层实现逻辑的复杂。   我们重温一下 volatile 关键字实现涉及到的知识点。从大的分类来说,其涉及两个知识点:多核心中数据的一致性,禁止指令的乱序执行。我们一个一个来看。    多核心中数据的一致性   现代处理器为了提高内存数据的访问速度,都会有自带的多级缓存,其位置在内存与处理器之间。   老的CPU会有两级内存(L1和L2),新的CPU会有三级内存(L1,L2,L3 )。其中: L1缓分成两种,一种是指令缓存,一种是数据缓存

C++的四种新式显示转换和一种旧式显示转换

我的未来我决定 提交于 2020-03-02 14:48:45
C++的四种新式显示转换和一种旧式显示转换 1、静态转换(static_cast) static_cast包含的转换类型有 典型的非强制变换 、 窄化(有信息丢失)变换 、 使用void*的强制转换 、 隐式类型 变换 、 类层次的静态定位 。static_cast是编译器允许的。 ‍ (1)典型的非强制变换 ‍ 从窄类型向宽类型的转换,如char向short int,int,long int,float,double,long double的转换。 char a = 1; long b = static_cast<long>(a); ‍ (2)窄化变换 ‍ 与第1种相反,从宽类型向窄类型的变换。 long b = 1; char a = static_cast<char>(b); (3)使用void*的强制变换 任何非 const 型指针都可以赋予 void*,void* 被用于对象的确切类型未知或者特定环境下对象类型发生变化的情 况下 ! 但 void* 不能直接被解除引用 , 因为没有类型信息可用来知道编译器怎么解释低层位模板 !void* 必须转换 成某类型的指针 , 在 C++ 中没有 void* 到特殊类型的自动转换 ! 必须显示强制转换 ! struct callback_param { void *vp; }; int a = 1; struct callback

java进阶(6)之从硬件底层剖析synchronized/volatile原理

不羁的心 提交于 2020-03-02 11:06:02
已知: java 中的 synchronized 关键字能保证可见性,有序性,原子性; volatile 关键字能保证可见性,有序性。 问题: 为什么 java 中的并发,在硬件层面不能保证,非要在 JVM 里处理呢? 它们在硬件层面是如何对应保证的呢? java 层面为啥要加这两个关键字才能保证 java 的并发特性呢? 指令重排序无法保证有序性 java 中的一行行代码,对应到硬件层面,就是一个个指令,现代处理器为了加快编译速度,有可能会乱序执行指令,这样也就无法保证代码的有序性了。 现代CPU缓存模型无法保证可见性和原子性 图解CPU缓存模型 简化版CPU缓存模型请移步: java进阶(4)之volatile关键字深入详解 1. 高速缓存中的 tag 表示数据所对应的内存地址, cacheline 表示数据本身, flag 表示数据状态(E表示 exclusive 写,S表示 shared 读) 2. Java 层面的读写数据,对应到硬件层面,就是主内存/高速缓存的读写; 3. 处理器与处理器之间的数据交互,或者说处理器与主内存的数据交互,是通过总线的嗅探机制来实现的,也可以说是缓存一致性协议(我把嗅探机制类比成 java 层面的 EventBus ); 4. 现代 CPU 为了加快读写速度,加入了写缓冲器和无效队列,但是这样的话,数据只有读写完成了,才能进入到高速缓存

__asm__ __volatile__ GCC的内嵌汇编语法 AT&T汇编语言语法

徘徊边缘 提交于 2020-03-02 06:40:07
开 发一个OS,尽管绝大部分代码只需要用C/C++等高级语言就可以了,但至少和硬件相关部分的代码需要使用汇编语言,另外,由于启动部分的代码有大小限 制,使用精练的汇编可以缩小目标代码的Size。另外,对于某些需要被经常调用的代码,使用汇编来写可以提高性能。所以我们必须了解汇编语言,即使你有可 能并不喜欢它。 如果你是计算机专业的话,在大学里你应该学习过Intel格式的8086/80386汇编,这里就不再讨论。如果我们选择的OS开发工具是GCC以及GAS的话,就必须了解AT&T汇编语言语法,因为GCC/GAS只支持这种汇编语法。 本书不会去讨论8086/80386的汇编编程,这类的书籍很多,你可以参考它们。这里只会讨论AT&T的汇编语法,以及GCC的内嵌汇编语法。 -------------------------------------------------------------------------------- 0.3.2 Syntax 1.寄存器引用 引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。 80386有如下寄存器: 8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp; 8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,

volatile for signal handler and multi-threading

∥☆過路亽.° 提交于 2020-03-02 06:28:07
问题 It is said volatile is needed for signal handler, e.g., volatile int flag = 1; // volatile is needed here? void run() { while(flag) { /* do someting... */ } } void signal_handler(int sig) { flag = 0; } int main() { signal(SIGINT, sig_handler); run(); // ... } It is said volatile is often not used in multithreading. But how about the similar case like above in multithreading: int flag = 1; // is volatile needed here? void thread_function() { while(flag) { /* do someting... */ } } int main() {

最牛X的GCC 内联汇编

别说谁变了你拦得住时间么 提交于 2020-03-02 05:55:44
1. 简介 1.1 版权许可 Copyright (C) 2003 Sandeep S. 本文档自由共享;你可以重新发布它,并且/或者在遵循自由软件基金会发布的 GNU 通用公共许可证下修改它;也可以是该许可证的版本 2 或者(按照你的需求)更晚的版本。 发布这篇文档是希望它能够帮助别人,但是没有任何担保;甚至不包括可售性和适用于任何特定目的的担保。关于更详细的信息,可以查看 GNU 通用许可证。 1.2 反馈校正 请将反馈和批评一起提交给 Sandeep.S。我将感谢任何一个指出本文档中错误和不准确之处的人;一被告知,我会马上改正它们。 1.3 致谢 我对提供如此棒的特性的 GNU 人们表示真诚的感谢。感谢 Mr.Pramode C E 所做的所有帮助。感谢在 Govt Engineering College 和 Trichur 的朋友们的精神支持和合作,尤其是 Nisha Kurur 和 Sakeeb S 。 感谢在 Gvot Engineering College 和 Trichur 的老师们的合作。 另外,感谢 Phillip , Brennan Underwood 和 colin@nyx.net ;这里的许多东西都厚颜地直接取自他们的工作成果。 2. 概览 在这里,我们将学习 GCC 内联汇编。这里内联表示的是什么呢?

AT&T的汇编格式&X86内联汇编

徘徊边缘 提交于 2020-03-02 04:47:09
AT&T的汇编格式 一 基本语法 语法上主要有以下几个不同. ★ 寄存器命名原则 AT&T: %eax Intel: eax ★源/目的操作数顺序 AT&T: movl %eax,%ebx Intel: mov ebx,eax ★常数/立即数的格式 AT&T: movl $_value,%ebx Intel: mov eax,_value 把_value的地址放入eax寄存器 AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d ★ 操作数长度标识 AT&T: movw %ax,%bx Intel: mov bx,ax ★寻址方式 AT&T: immed32(basepointer,indexpointer,indexscale) Intel: [basepointer + indexpointer*indexscale + imm32) Linux工作于保护模式下,用的是32位线性地址,所以在计算地址时不用考虑segment:offset的问题.上式中的地址应为: imm32 + basepointer + indexpointer*indexscale 下面是一些例子: ★直接寻址 AT&T: _booga ; _booga是一个全局的C变量 注意加上$是表示地址引用,不加是表示值引用. 注:对于局部变量,可以通过堆栈指针引用. Intel

【设计模式】单例模式

痴心易碎 提交于 2020-03-01 20:17:38
单例模式 优点 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。 缺点 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。 使用场景 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。 实现方式 1 懒汉式 注 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 getInstance() 的性能对应用程序很关键。 public class LazySingleton { //没有volatile其他线程可能由于指令重排访问到还没有初始化的对象 //volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; private static volatile LazySingleton lazySingleton = null ; private LazySingleton ( ) { } public static LazySingleton getInstance ( ) { //双检锁/双重校验锁(DCL,即 double-checked locking) if ( lazySingleton == null ) {

ConcurrentHashMap 1.8

久未见 提交于 2020-03-01 09:37:42
static class Node < K , V > implements Map . Entry < K , V > { final int hash ; final K key ; // 与hashmap不同 volatile V val ; volatile Node < K , V > next ; // 与hashmap不同 transient volatile Node < K , V > [ ] table ; public V get ( Object key ) { Node < K , V > [ ] tab ; Node < K , V > e , p ; int n , eh ; K ek ; int h = spread ( key . hashCode ( ) ) ; if ( ( tab = table ) != null && ( n = tab . length ) > 0 && // 与hashmap不同 ( e = tabAt ( tab , ( n - 1 ) & h ) ) != null ) { if ( ( eh = e . hash ) == h ) { if ( ( ek = e . key ) == key || ( ek != null && key . equals ( ek ) ) ) return e . val ; }