培训网站建设平台汕尾网站seo

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

培训网站建设平台,汕尾网站seo,wordpress虚化主题,最大源码网站文章目录 1、volatile可见性案例2、线程工作内存与主内存之间的原子操作3、volatile变量不具有原子性案例4、无原子性的原因分析#xff1a;i5、volatile变量小总结6、重排序7、volatile变量禁重排的案例8、日常使用场景9、总结 volatile变量的特点#xff1a; 可见性禁重排无… 文章目录 1、volatile可见性案例2、线程工作内存与主内存之间的原子操作3、volatile变量不具有原子性案例4、无原子性的原因分析i5、volatile变量小总结6、重排序7、volatile变量禁重排的案例8、日常使用场景9、总结 volatile变量的特点 可见性禁重排无原子性 1、volatile可见性案例 volatile的可见性即保证不同线程对某一个变量一旦完成更改其他线程立即可见因为会从线程的工作内存立马刷到主内存。Demo程序 public class VolatileDemo1 {static boolean flag true;public static void main(String[] args) throws InterruptedException {new Thread(() - {System.out.println(Thread.currentThread().getName() come in …);while (flag) {}System.out.println(flag被设置为false线程任务执行结束);}, t1).start();TimeUnit.SECONDS.sleep(2);flag false;System.out.println(Thread.currentThread().getName() 线程已将flag改为false);} } flag已被main线程改为false但t1线程没有收到通知而一直在循环 变量改为volatile变量 static volatile boolean flag true;重新运行 t1程序可正常停止了原因就是加了volatile后flag变量有了可见性main线程改完后t1可以知道这个变更。 线程t1中为何看不到被主线程main修改为false的flag的值?可能原因有 主线程修改了flag之后没有将其刷新到主内存所以t1线程看不到主线程将flag刷新到了主内存但是t1一直自娱自乐读取的是自己工作内存中fag的值没有去主内存中更新获取flag最新的值 想解决这个问题需要 线程中修改了自己工作内存中的副本之后立即将其刷新到主内存工作内存中每次读取共享变量时都去主内存中重新读取然后拷贝到工作内存。 使用volatile修饰共享变量就可以达到上面的效果被volatile修改的变量有以下特点 线程中读取的时候每次读取都会去主内存中读取共享变量最新的值然后将其复制到工作内存线程中修改了工作内存中变量的副本修改之后会立即刷新到主内存 2、线程工作内存与主内存之间的原子操作 read作用于主内存将变量的值从主内存传输到工作内存主内存到工作内存 load作用于工作内存将read从主内存传输的变量值放入工作内存变量副本中即数据加载 use作用于工作内存将工作内存变量副本的值传递给执行引擎每当JVM遇到需要该变量的字节码指令时就会执行该操作 assign作用于工作内存将从执行引擎接收到的值赋值给工作内存变量每当JVM遇到一个给变量赋值字节码指令时就会执行该操作 store作用于工作内存将赋值完毕的工作变量的值写回给主内存 write作用于主内存将store传输过来的变量值赋值给主内存中的变量 lock作用于主内存将一个变量标记为一个线程独占的状态只是写时候加锁就只是锁了写变量的过程 unlock作用于主内存把一个处于锁定状态的变量释放然后才能被其他线程占用
以上面的flag变量为例说明volatile变量的读写过程 step1先从主内存中读到然后load加载到线程t1自己的工作内存read、load成对出现,开始use到while循环然后一直循环step2main线程同样的操作从主内存load到自己的工作内存但它配合CPU完成了对flag的赋值assign并存储store到自己的工作内存step3接下来main线程准备要把这个变更写回主内存了此时必须加锁lock写完后解锁unlockstep4上一步的加锁后会清空其他线程工作内存这个变量的值在使用变量前必须重新load或者assign因此t1线程可以获取到最新的变量值 3、volatile变量不具有原子性案例 volatile变量的复合操作不具有原子性比如number 先看不用volatile的 class MyNumber{int number;public synchronized void addPlus(){number;} } 同时开十个线程每个线程调用1000次addPlus方法 public class VolatileDemo1 {public static void main(String[] args) throws InterruptedException {MyNumber myNumber new MyNumber();for (int i 0; i 10; i) {new Thread(() - {for (int j 0; j 1000; j) {myNumber.addPlus();}},String.valueOf(i)).start();}//给上面线程的计算时间Thread.sleep(2000);System.out.println(myNumber.number);} }正常输出10000修改不用synchronized变量改为带volatile关键字的 运行接近10000但不会等于10000 4、无原子性的原因分析i 在没有加锁的控制时就没有原子性的保证synchronized依靠monitor来保证同一时间只能有一个线程来操作当线程1对主内存对象发起read操作到write操作第一套流程的时间里线程2随时都有可能对这个主内存对象发起第二套换作如下图虚线表示线程2的读取的可能时机 从源代码来看number只有一行但对应到底层则是 而线程自己的工作内存里数据加载、计算和赋值这三步不是原子操作是可能被分开的。 对于volatile变量的可见性JVM只是保证从主内存加载到线程的工作内存的值是最新的即数据加载这一步是最新的对比上面的案例主内存中volatile修饰的变量number5此时线程A和线程B都能读线程A要进行1线程B也要进行1但线程B在CPU的调度下一口气走完了数据加载、计算和赋值这三步并刷回主内存此时主内存number6而线程B比较慢刚做完1的计算但由于volatile的可见性主内存中已经等于6了线程B的值作废去主内存重读number 6然后线程B一路number17并刷到主内存一看两个线程做了三次1的操作结果number只是从5变到7这就是上面结果接近10000但小于10000的原因 由此可见volatile解决的是变量读时的可见性问题但无法保证原子性对于多线程修改主内存共享变量的场景必须使用加锁同步加锁同一时间最多只能有一个线程进来每次1都能走完写的整体流程因为其他线程进不来没有上面那种刚完成数据加载然后数据就被别的线程改了并刷到主内存导致自己刚加载的作废的情况。再从i的字节码来看 原子性指的是一个操作是不可中断的即使是在多线程环境下一个操作一旦开始就不会被其他线程影响。 很明显i是个复合操作分了三步不具备原子性。如果第二个线程在第一个线程读取旧值和新值写回期间读取了i的值就会出现两个线程同时对一个值做加1的情况。比如i6时线程1完成了61在写回新值前线程2读取了数据继续61不管是线程1和线程2谁先写回主内存哪怕都写回主内存也是个7何况volatile下慢的那一个线程会去主内存重读都是61做了两次但最后等于7相当于加了1次 5、volatile变量小总结 volatile变量不适合参与到依赖当前值的运算比如i i1 ; i之类的 依靠volatile变量的可见性其适合用于保存某个状态的Boolean值 6、重排序 重排序是编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段但前提是不能改变原语义或者说指令不能存在数据依赖关系数据依赖关系即若两个操作访问同一变量且这两个操作中有一个为写操作此时两操作间就存在数据依赖证。 若存在数据依赖关系禁止重排序 重排序发生会导致程序运行结果不同。 7、volatile变量禁重排的案例 Demo public class VolatileDemo2 {int i 0;volatile boolean flag false;public void write(){i 2;flag true;}public void read(){if(flag){System.out.println(—i i);}}} 在每个volatile写操作后面插入写读屏障 在每个volatile读操作后面插入读写屏障 如此程序原来的的语义就得到的保证 8、日常使用场景 Case1单一赋值可以但是含有复合运算赋值如i不适用 volatile int a 10;Case2在高并发里面如果是靠变量来通知其他线程来改变后续动作的那可利用volatile变量的可见性做状态标志位判断业务是否结束 volatile boolean flag false;做为一个布尔状态标志判断业务是否该结束了 Case3开销较低的读写锁策略 get和increment方法都加synchronized安全性是保证了但太重性能下降太多 public class Counter{private int vlaue;public synchronized int getValue(){ //读也得先拿对象锁return value;}public synchronized int incerment(){return value;} }考虑synchronized结合volatile此时也可以每次都读到最新的数据即使没加锁 public class Counter{private volatile int vlaue; //volatilepublic int getValue(){ //利用volatile可见性保证并发下也能读取到最新值return value;}public synchronized int incerment(){ //利用synchronized保证复合操作的原子性return value;} }Case4DCL双端检查锁的禁重排 参考经典文章这篇循序渐进都讲明白了 【单例模式下的DCL】 大概贴下代码双端检查锁的普通代码 问题 重排序后先给变量指向了分配的内存地址在初始化对象前多线程下其他线程获取判断对象是否为null很明显对象内存地址不为null了但其实对象还没new后面用它就会空指针 需要给这个变量加volatile关键字来禁止指令重排 Case5对象属性修改原子类中更新的对象属性必须使用 public volatile 修饰符 9、总结 凭什么java写了一个volatile关键字就可以让系统底层加入内存屏障?两者关系怎么勾搭上的?什么是内存屏障内存屏障是一种 屏障指令它使得 CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束 。 也叫内存栅栏或栅栏指令。 内存屏障能干嘛阻止屏障两边的指令重排序写数据时加入屏障强制将线程私有工作内存的数据刷回主物理内存读数据时加入屏障线程私有工作内存的数据失效重新到主物理内存中获取最新数据 内存屏障的四大指令在每一个volatile写操作前面插入一个StoreStore屏障在每一个volatile写操作后面插入一个StoreLoad屏障在每一个volatile读操作后面插入一个LoadLoad屏障在每一个volatile读操作后面插入一个LoadStore屏障 总之volatile即可见性、禁重排、以及无原子性 volatile 写之前的操作都禁止重排序到 volatile 之后volatile 读之后的操作都禁止重排序到 volatile 之前volatile 写之后 volatile 读禁止重排序