青岛工程建设监理公司网站,公司新成立想要搭建网站怎么做,网站程序免费下载,福州设计公司排行一、volatile
volatile是Java虚拟机提供的轻量级的同步机制#xff0c;它有#xff13;个特性#xff1a; #xff11;#xff09;保证可见性 #xff12;#xff09;不保证原子性 #xff13;#xff09;禁止指令重排
当写一个volatile变量时#xff0c;JMM会把该…一、volatile
volatile是Java虚拟机提供的轻量级的同步机制它有个特性 保证可见性 不保证原子性 禁止指令重排
当写一个volatile变量时JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中当读一个volatile变量时JMM会把该线程对应的本地内存设置为无效直接从主内存中读取共享变量
所以volatile的写内存语义是直接刷新到主内存中读的内存语义是直接从主内存中读取
1. 可见性
保证不同线程对这个变量进行操作时的可见性即变量一旦改变所有线程立即可见 。
使用volatile修饰共享变量被volatile修改的变量有以下特点:
线程中读取的时候每次读取都会去主内存中读取共享变量最新的值然后将其复制到工作内存线程中修改了工作内存中变量的副本修改之后会立即刷新到主内存
从volatile变量的读写过程分析 要use(使用)一个变量的时候必需load(载入)要载入的时候必需从主内存read(读取)这样就解决了读的可见性。写操作是把assign(赋值和store(存储)做了关联(在assign(赋值)后必需store(存储))store(存储)后write(写入)。 也就是做到了给一个变量赋值的时候一串关联指令直接把变量值写到主内存。 就这样通过用的时候直接从主内存取在赋值到直接写回主内存做到了内存可见性。 2. 无原子性
原子性指的是一个操作是不可中断的即使是在多线程环境下一个操作一旦开始就不会被其他线程影响。
多线程环境下“数据计算”和“数据赋值”操作可能多次出现即操作非原子。若数据在加载之后若主内存count变量发生修改之后由于线程工作内存中的值在此前已经加载从而不会对变更操作做出相应变化即私有内存和公共内存中变量不同步进而导致数据不一致。
对于volatile变量JVM只是保证从主内存加载到线程工作内存的值是最新的也就是数据加载时是最新的。由此可见volatile解决的是变量读时的可见性问题但无法保证原子性对于多线程修改共享变量的场景必须使用加锁同步。
以i为例不具备原子性该操作是先读取值然后写回一个新值相当于原来的值加上1,分3步完成。
如果第二个线程在第一个线程读取旧值和写回新值期间上图所指三步期间读取i的域值那么第二个线程就会与第一个线程一起看到同一个值并执行相同值的加1操作这也就造成了线程安全失败因此对于add方法必须使用synchronized修饰以便保证线程安全。
从volatile变量的读写过程分析 read-load-use和assign-store-write成为了两个不可分割的原子操作但是在use和assign之间依然有极小的一段真空期有可能变量会被其他线程读取导致写丢失一次 3. 指令禁重排
重排序
是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段有时候会改变程序语句的先后顺序。
不存在数据依赖关系可以重排序存在数据依赖关系禁止重排序 。
但重排后的指令绝对不能改变原有的串行语义。 数据依赖性若两个操作访问同一变量且这两个操作中有一个为写操作此时两操作间就存在数据依赖性 重排序的分类和执行流程 : 编译器优化的重排序编译器在不改变单线程串行语义的前提下可以重新调整指令的执行顺序 指令级并行的重排序处理器使用指令级并行技术来将多条指令重叠执行若不存在数据依赖性处理器可以改变语句对应机器指令的执行顺序 内存系统的重排序由于处理器使用缓存和读/写缓冲区这使得加载和存储操作看上去可能是乱序执行 volatile有关禁重排的行为 当第一个操作为volatile读时不论第二个操作是什么都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前volatile读之后的操作都禁止重排序到volatile之前 当第二个操作为volatile写时不论第一个操作是什么都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatile写之后 (volatile写之前的操作都禁止重排序到volatile之后) 当第一个操作为volatile写时第二个操作为volatile读时不能重排。(volatile写之后volatile读禁止重排序的)
内存屏障四大指令插入情况 在每个volatile写操作的前面插入一个StoreStore屏障保证在volatile写之前其前面的所有普通写操作都已经刷新到主内存中。 在每个volatile写操作的后面插入一个StoreLoad屏障避免volatile写与后面可能有的volatile读/写操作重排序 在每个volatile读操作的后面插入一个LoadLoad屏障禁止处理器把上面的volatile读与下面的普通读重排序。 在每个volatile读操作的后面插入一个LoadStore屏障禁止处理器把上面的volatile读与下面的普通写重排序。
二、如何正确使用volatile
由于volatile变量只能保证可见性在不符合以下两条规则的运算场景中我们仍然要通过加锁(使用synchronized、java.util.concurrent中的锁或原子类)来保证原子性
运算结果并不依赖变量的当前值或者能够确保只有单一的线程修改变量的值。变量不需要与其他的状态变量共同参与不变约束。
1. 单一赋值可以但是含复合运算赋值不可以(i之类)
volatile int a 10;2. 状态标志判断业务是否结束
使用作为一个布尔状态标志用于指示发生了一个重要的一次性事件例如完成初始化或任务结束
理由状态标志并不依赖于程序内任何其他状态且通常只有一种状态转换
例子判断业务是否结束 volatile boolean flag false 3. 开销较低的读写锁策略
使用当读远多于写结合使用内部锁和volatile变量来成少同步的开销 。
理由
利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性。
4. DCL双端锁的发布
隐患多线程环境下由于重排序该对象可能还未完成初始化就被其他线程读取
修正方法1加volatile也即正确的DCL双端锁
原理利用volatile, 禁止初始化对象和设置singleton指向内存空间的重排序
修正方法2静态内部类