当前位置: 首页 > news >正文

吴中区住房和城乡建设局网站做网站 内容越多越好

吴中区住房和城乡建设局网站,做网站 内容越多越好,武进做网站的公司,密云广州网站建设上篇我们讨论了JMM中的工作内存和主内存、内存直接的交互指令#xff0c;以及指令之间的顺序规则。 本篇将会以上篇为基础#xff0c;详细介绍并发编程中的三个重要概念/工具#xff1a;Volatile、原子性/可见性和先行发生#xff08;happens-before)原则。 volatile型变量…上篇我们讨论了JMM中的工作内存和主内存、内存直接的交互指令以及指令之间的顺序规则。 本篇将会以上篇为基础详细介绍并发编程中的三个重要概念/工具Volatile、原子性/可见性和先行发生happens-before)原则。 volatile型变量的特殊规则 关键字volatile是Java虚拟机提供的最轻量级的同步机制。 由于它难以正确完整地理解大多数场景我们都会避免使用它。遇到需要处理多线程数据竞争问题的时候一律使用 synchronized来进行同步。 但是了解volatile变量的语义对后面理解多线程操作的其他特性很有意义在本节仔细介绍volatile。 volatile变量具备两项特性**可见性和禁止指令重排序优化。**下面我们详细介绍这两个特性。 可见性 volatile会保证被它修饰的变量对所有线程的可见性。 这里的“可见性”是指当一条线程修改了这个变量的值新值对于其他线程来说是可以立即得知的。 普通变量并不能做到这一点普通变量的值在线程间传递时均需要通过主内存来完成。、 比如 线程A修改一个普通变量的值然后向主内存进行回写另外一条线程B在线程A回写完成了之后再对主内存进行读取操作新变量值才会对线程B可见。 volatile变量的可见性经常会被误解被误以为下面这句话 “volatile 变量对所有线程是立即可见的对volatile变量所有的写操作都能立刻反映到其他线程之中。换句话 说volatile变量在各个线程中是一致的所以基于volatile变量的运算在并发下是线程安全的”。 这句话前半部分没问题但并不能得出“基于volatile变量的运算在并发下是线程安全的”这样的结论。 volatile变量在各个线程的工作内存中是不存在一致性问题的。 从物理存储的角度看各个线 程的工作内存中volatile变量也可以存在不一致的情况但由于每次使用之前都要先刷新执行引擎看 不到不一致的情况因此可以认为不存在一致性问题 但是Java里面的运算操作符并非原子操作 这导致volatile变量的运算在并发下一样是不安全的。我们可以通过一段简单的演示来说明原因 /** * volatile变量自增运算测试 * * author zzm */ public class VolatileTest { public static volatile int race 0; public static void increase() { race; }private static final int THREADS_COUNT 20; public static void main(String[] args) { Thread[] threads new Thread[THREADS_COUNT]; for (int i 0; i THREADS_COUNT; i) { threads[i] new Thread(new Runnable() { Override public void run() { for (int i 0; i 10000; i) { increase(); }} }); threads[i].start(); } // 等待所有累加线程都结束 while (Thread.activeCount() 1) Thread.yield(); System.out.println(race); } }这段代码发起了20个线程每个线程对race变量进行10000次自增操作如果这段代码能够正确并 发的话最后输出的结果应该是200000。事实上每次运行程序输出的结果都不一样都是一个小于200000的数字。 问题在自增运算“race”之中虽然它是只有一行代码但在在Class文件中是由4条字节码指令构成 public static void increase(); Code:Stack2, Locals0, Args_size0 0: getstatic #13; //Field race:I 3: iconst_1 4: iadd 5: putstatic #13; //Field race:I 8: return LineNumberTable: line 14: 0 line 15: 8 当然使用字节码来分析并发问题仍然是不严谨的因为即使编译出来只有一条字 节码指令也并不意味执行这条指令就是一个原子操作。 什么场景可以不加锁保证原子性 由于volatile变量只能保证可见性在不符合以下两条规则的运算场景中我们仍然要通过加锁使用synchronized、java.util.concurrent中的锁或原子类来保证原子性即如果符合下面两条规则就可以不加锁保证原子性 运算结果并不依赖变量的当前值或者能够确保只有单一的线程修改变量的值。变量不需要与其他的状态变量共同参与不变约束。 禁止指令重排序优化 使用volatile变量的第二个语义是禁止指令重排序优化。 虽然普通的变量仅会保证在该方法的执行过程 中所有依赖赋值结果的地方都能获取到正确的结果但却不能保证变量赋值操作的顺序与程序代码中的执行顺序一致 因此在同一个线程的方法执行过程中无法感知到这点这就是Java内存模型中描述的 所谓“线程内表现为串行的语义”Within-Thread As-If-Serial Semantics 通过一个例子来看以下为何指令重排序会干扰程序的并发执行 Map configOptions; char[] configText; // 此变量必须定义为volatile volatile boolean initialized false; // 假设以下代码在线程A中执行 // 模拟读取配置信息当读取完成后 // 将initialized设置为true,通知其他线程配置可用 configOptions new HashMap(); configText readConfigFile(fileName); processConfigOptions(configText, configOptions); initialized true; // 假设以下代码在线程B中执行 // 等待initialized为true代表线程A已经把配置信息初始化完成 while (!initialized) { sleep(); } // 使用线程A中初始化好的配置信息 doSomethingWithConfig(); 上面代码开发中常见配置读取过程只是我们在处理配置文件时一般不会出现并发所以没有察觉这会有问题。 如果定义initialized变量时没有使用volatile修饰就可能会由于指令重排序的优化导致位于线程A中最后一条代码“initializedtrue”被提前执行. 这样在线程B中使用配置信息的代码 就可能出现错误而volatile关键字则可以避免此类情况的发生 指令重排序是并发编程中最容易导致开发人员产生疑惑的地方之一。 双锁检测模式的单例 下面是一段标准的双锁检测Double Check LockDCL单例可以观察加入volatile 和未加入volatile关键字时所生成的汇编代码的差别 public class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } }return instance; }public static void main(String[] args) { Singleton.getInstance(); } } 编译后这段代码对instance变量赋值的部分如下所示 0x01a3de0f: mov $0x3375cdb0,%esi ;...beb0cd75 33 ; {oop(Singleton)} 0x01a3de14: mov %eax,0x150(%esi) ;...89865001 0000 0x01a3de1a: shr $0x9,%esi ;...c1ee09 0x01a3de1d: movb $0x0,0x1104800(%esi) ;...c6860048 100100 0x01a3de24: lock addl $0x0,(%esp) ;...f0830424 00 ;*putstatic instance ; - Singleton::getInstance24通过对比发现有volatile修饰的变量在赋值后前面mov%eax0x150(%esi)这句便是赋值操作多执行了一个“lock addl$0x0(%esp)”操作这个操作的作用相当于一个内存屏障 Memory Barrier或Memory Fence。 重排序时不能把后面的指令重排序到内存屏障之前的位置。 只有一个处理器访问内 存时并不需要内存屏障但如果有两个或更多处理器访问同一块内存且其中有一个在观测另一 个就需要内存屏障来保证一致性了。 如何禁止指令重排序 指令重排序定义 从硬件架构上讲指令重排序是指处理器采用了允许将多条指令 不按程序规定的顺序分开发送给各个相应的电路单元进行处理。 但并不是说指令任意重排处理器必须能正确处理指令依赖情况保障程序能得出正确的执行结果。 譬如指令1把地址A中的值加10指令2 把地址A中的值乘以2指令3把地址B中的值减去3这时指令1和指令2是有依赖的它们之间的顺序 不能重排——(A10)2与A210显然不相等但指令3可以重排到指令1、2之前或者中间只要保证 处理器执行后面依赖到A、B值的操作时能获取正确的A和B值即可。所以在同一个处理器中重排序 过的代码看起来依然是有序的。 volatile变量的指令顺序规则 假定T是一个线程V和W分别表示两个volatile型变量那么在进行read、load、use、assign、store和write操作时需要满足如下规则 只有当线程T对变量V执行的前一个动作是load的时候线程T才能对变量V执行use动作并且 只有当线程T对变量V执行的后一个动作是use的时候线程T才能对变量V执行load动作。线程T对变量V的use动作可以认为是和线程T对变量V的load、read动作相关联的必须连续且一起出现。 这条规则要求在工作内存中每次使用V前都必须先从主内存刷新最新的值用于保证能看见其他线程对变量V所做的修改。 只有当线程T对变量V执行的前一个动作是assign的时候线程T才能对变量V执行store动作并且只有当线程T对变量V执行的后一个动作是store的时候线程T才能对变量V执行assign动作。线程T对变量V的assign动作可以认为是和线程T对变量V的store、write动作相关联的必须连续且一起出现。 这条规则要求在工作内存中每次修改V后都必须立刻同步回主内存中用于保证其他线程可以看到自己对变量V所做的修改。 假定动作A是线程T对变量V实施的use或assign动作假定动作F是和动作A相关联的load或store动 作假定动作P是和动作F相应的对变量V的read或write动作与此类似假定动作B是线程T对变量W 实施的use或assign动作假定动作G是和动作B相关联的load或store动作假定动作Q是和动作G相应的.。对变量W的read或write动作。如果A先于B那么P先于Q。 这条规则要求volatile修饰的变量不会被指令重排序优化从而保证代码的执行顺序与程序的顺序相同。 Volatile优点 在某些情况下volatile的同步机制的性能确实要优于锁 使用synchronized关键字或java.util.concurrent包里面的锁。 但是由于虚拟机对锁实行的许多消除和 优化使得我们很难确切地说volatile就会比synchronized快上多少。 如果让volatile自己与自己比较那可以确定一个原则volatile变量读操作的性能消耗与普通变量几乎没有什么差别但是写操作则可能 会慢上一些。 因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。 不过即便如此大多数场景下volatile的总开销仍然要比锁来得更低。我们在volatile与锁中选择的唯一判断依据仅仅是volatile的语义能否满足使用场景的需求。 针对long和double型变量的特殊规则 Java内存模型要求lock、unlock、read、load、assign、use、store、write这八种操作都具有原子性。 但是对于64位的数据类型long和double在模型中特别定义了一条宽松的规定 允许虚拟机将没有 被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性这就是所谓的“long和double的非原子性协定”Non-Atomic Treatment of double and long Variables。 经过实际测试[1]在目前主流平台下商 用的64位Java虚拟机中并不会出现非原子性访问行为但是对于32位的Java虚拟机譬如比较常用的32 位x86平台下的HotSpot虚拟机对long类型的数据确实存在非原子性访问的风险。从JDK 9起 HotSpot增加了一个实验性的参数-XXAlwaysAtomicAccesses这是JEP 188对Java内存模型更新的 一部分内容来约束虚拟机对所有数据类型进行原子性的访问。 原子性、可见性与有序性 Java内存模型是JMM围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征并发三大特征来建立的。 下面逐个来看一下哪些操作实现了这三个特性 原子性Atomicity 由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个. 大致可以认为基本数据类型的访问、读写都是具备原子性的例外就是long和double的非原子性 协定,无须太过在意这些几乎不会发生的例外情况。 如果需要一个更大范围的原子性保证代码块Java内存模型还提供了lock和 unlock操作来满足这种需求。 尽管虚拟机未把lock和unlock操作直接开放给用户使用但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作。这两个字节码指令反映到Java 代码中就是同步块——synchronized关键字因此在synchronized块之间的操作也具备原子性。 可见性Visibility 可见性就是指当一个线程修改了共享变量的值时其他线程能够立即得知这个修改。 在volatile一节中已经详细讨论过这一点。Java内存模型是通过在变量修改后将新值同步回主内存在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。 无论是 普通变量还是volatile变量都是如此。 普通变量与volatile变量的区别是volatile的特殊规则保证了新值能立即同步到主内存以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性而普通变量则不能保证这一点。 除了volatile之外Java还有两个关键字能实现可见性它们是synchronized和final。 同步块的可见性是由“对一个变量执行unlock操作之前必须先把此变量同步回主内存中执行store、write操 作”这条规则获得的。 final关键字的可见性是指被final修饰的字段在构造器中一旦被初始化完成并且构造器没有把“this”的引用传递出去this引用逃逸是一件很危险的事情其他线程有可能通 过这个引用访问到“初始化了一半”的对象那么在其他线程中就能看见final字段的值。 如下图代码所示变量i与j都具备可见性无须同步即可被其他线程正确访问 public static final int i; public final int j; static { i 0; // 省略后续动作 }{ // 也可以选择在构造函数中初始化 j 0; // 省略后续动作 }有序性Ordering 有序性在volatile中也比较详细地讨论过了。 Java程序中天然的有序性可以总结为一句话如果在本线程内观察所有的操作都是有序的如果在一个线程中观察另一个线程所有的操作都是无序的。 前半句是指“线程内似表现为串行的语义”Within-Thread As-If-Serial Semantics后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。 Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。 volatile关键字本身就包含了禁止指令重排序的语义。 而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的这个规则决定了持有同一个锁的两个同步块只能串行地进入。 介绍完并发中三种重要的特性发现synchronized关键字在需要这三种特性的时候都可以作为其中一种的解决方案看起来很“万能”事实上绝大部分并发控制操作都能使用synchronized来完成。synchronized的“万能”也间接造就了它被程序员滥用的局面越“万能”的并发控制通常会伴随着越大的性能影响。 先行发生原则Happens-Before) 如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成那么有很多操作都将会变得非常啰嗦。 但是我们在编写Java并发代码的时候并没有察觉到这一点这是因为Java语言中有一 个“先行发生”Happens-Before的原则。 这个原则非常重要它是判断数据是否存在竞争线程是否安全的非常有用的手段。 依赖这个原则我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题而不需要陷入Java内存模型苦涩难懂的定义之中。 先行发生是Java内存模型中定义的两项操作之间的偏序关系比如说操作A先行发生于操作B其实就是说在发生操作B之前操作A产生的影响能被操作B观察到 “影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。 通过下面的例子深入了解一下为什么需要先行发生 // 以下操作在线程A中执行 i 1; // 以下操作在线程B中执行 j i; // 以下操作在线程C中执行 i 2;假设线程A中的操作“i1”先行发生于线程B的操作“ji”。 那我们就可以确定在线程B的操作执行后变量j的值一定是等于1。 得出这个结论的依据有两个一是根据先行发生原则“i1”的结果可以被观察到二是线程C还没登场线程A操作结束之后没有其他线程会修改变量i的值。 现在再来开看线程C依然保持线程A和B之间的先行发生关系而C出现在线程A和B的操作之间但是C与B没有先行发生关系那j的值会是多少呢 答案是不确定1和2都有可能因为线程C对变量i的影响可能会被线程B观察到也可能不会这时候线程B就存在读取到过期数据的风险不具备多线程安全性。 上面说得是多线程的但是我们提到单线程内也会指令重排序如果没有对应的规则岂不是乱套了吗所以需要JMM内置一些天然的先行发生原则。不仅用于多线程还用用于单线程 这些先行发生关系可以在编码中直接使用。如果两个操作之间的关系不在此列并且无法从下列规则推导出来则它们就没有顺序性保障虚拟机可以对它们随意地进行重排序 程序次序规则Program Order Rule在一个线程内按照控制流顺序书写在前面的操作**先行 ** 发生于书写在后面的操作。注意这里说的是控制流顺序而不是程序代码顺序因为要考虑分支、循 环等结构。 只是控制流顺序单个操作内的字节码还是可能会重排序,参考双锁单例模式 管程锁定规则Monitor Lock Rule一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”而“后面”是指时间上的先后。volatile变量规则Volatile Variable Rule对一个volatile变量的写操作先行发生于后面对这个变量的读操作这里的“后面”同样是指时间上的先后。线程启动规则Thread Start RuleThread对象的start()方法先行发生于此线程的每一个动作。线程终止规则Thread Termination Rule线程中的所有操作都先行发生于对此线程的终止检测我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。线程中断规则Thread Interruption Rule对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生可以通过Thread::interrupted()方法检测到是否有中断发生。对象终结规则Finalizer Rule一个对象的初始化完成构造函数执行结束先行发生于它的finalize()方法的开始。传递性Transitivity如果操作A先行发生于操作B操作B先行发生于操作C那就可以得出操作A先行发生于操作C的结论。 Java无须任何同步手段保障就能成立的先行发生规则有且只有上面这些下面演示一下如何使用这些规则去判定操作间是否具备顺序性。 对于读写共享变量的操作来说就是线程是否安全。 还可以从下面这个例子中感受一下“时间上的先后顺序”与“先行发生”之间有什么不同 private int value 0; pubilc void setValue(int value){ this.value value; } public int getValue(){ return value; } 上面一组再普通不过的getter/setter方法假设存在线程A和B线程A先时间上的先后调用了setValue(1)然后线程B调用了同一个对象的getValue()那么线程B收到的返回值是什么 我们依次分析一下先行发生原则中的各项规则 由于两个方法分别由线程A和B调用不在一个线程中所以程序次序规则在这里不适用由于没有同步块自然就不会发生lock和unlock操作所以管程锁定规则不适用由于value变量没有被volatile关键字修饰所以volatile变量规则不适用后面的线程启动、终止、中断规则和对象终结规则也和这里完全没有关系。因为没有一个适用的先行发生规则所以最后一条传递性也无从谈起 因此我们可以判定尽管线程A在操作时间上先于线程B但是无法确定线程B中getValue()方法的返回结果换句话说这里面的操作不是线程安全的。 怎么修复这个问题 我们至少有两种比较简单的方案可以选择 要么把getter/setter方法都定义为synchronized方法这样就可以套用管程锁定规则要么把value定义为volatile变量由于setter方法对value的修改不依赖value的原值满足volatile关键字使用场景这样就可以套用volatile变量规则来实现先行发生关系。 通过上面的例子我们可以得出结论一个操作“时间上的先发生”不代表这个操作会是“先行发生”。 那如果一个操作“先行发生”是否就能推导出这个操作必定是“时间上的先发生”呢 这个推论也是不成立的。 一个典型的例子就是多次提到的“指令重排序 // 以下操作在同一个线程中执行 int i 1; int j 2; 上面代码中两条赋值语句在同一个线程之中根据程序次序规则。 int i1”的操作先行发生于“int j2”但是“int j2”的代码完全可能先被处理器执行这并不影响先行发生原则的正确性 因为我们在这条线程之中没有办法感知到这一点。 上面两个例子综合起来证明了一个结论时间先后顺序与先行发生原则之间基本没有因果关系 所以我们衡量并发安全问题的时候不要受时间顺序的干扰一切必须以先行发生原则为准。
http://www.hkea.cn/news/14338098/

相关文章:

  • 卖东西的网站怎么建设黄埔区建设局网站
  • 免费企业建站模板建设公司和建筑公司有什么区别
  • 怎么制造网站百度推广登录入口
  • 全国村级网站建设sem推广是什么意思
  • 做网站的咋挣钱宁波网站开发公司怎么样
  • 无锡网站建设服务怎么找需要做网站的客户
  • 如何利用NAS做网站西南网架公司
  • 安徽省建设厅网站域名权威发布是鼠头
  • 网站中的幻灯片ie6显示 ie7如何兼容深圳手机网站设计公司
  • 建设微信商城网站wordpress调用产品图片
  • 自主建设公司网站wordpress id连续插件
  • 视频网站做板块栏目重庆网上房地产查询备案价
  • c 网站开发简单实例教程手机网站二级导航菜单
  • 润滑油东莞网站建设哈尔滨建设发展集团有限责任公司
  • 重庆电商网站建设费用成都服务器租赁
  • 单位网站建设自查报告范文关于网站建设的电话销售话术
  • 做企业网站怎么备案flash网站引导页面制作
  • 用DW做的网站生成链接洒长春菩网站建设
  • 成品网站源码68w68游戏wordpress知名博客主体
  • 从化网站建设在网站后台管理系统里产品说明怎么添加图片
  • 深圳建设网站top028哪些网站做的不好
  • 怎么做公司宣传网站wordpress零基础
  • 端口扫描站长工具本土建站工作室
  • 个人网站制作源代码下载传奇手游官方网站
  • 三室一厅二卫装修效果图seo行业岗位有哪些
  • 广西建设厅考试网站如何申请注册企业邮箱
  • 四合一网站建设源码河北网站优化建设
  • 用python做网站的多吗网站如何做流媒体
  • 建筑企业登录哪个网站站长统计app软件大全
  • 网站建设 泰安网站图片快速加载