来源:blog.csdn.net/fumitzuki/article/details/81630048volatile关键字是JVM提供的最轻量级的同步机制。不像被滥用的synchronized,我们不习惯使用它。正确完整地理解它并不容易。Java内存模型Java内存模型由Java虚拟机规范定义,用于屏蔽各个平台的硬件差异。简而言之:所有变量都存储在主内存中。每个线程都有自己的工作内存,它在主内存中保存了线程使用的变量的副本。线程不能直接读写主内存中的变量,所有的操作都在工作内存中完成。线程、主内存、工作内存的交互关系如图所示。内存之间有很多交互操作,与volatile相关的操作有:动作使用加载(Loading):作用于工作内存的变量,即将读操作从主内存中获取的变量值放入工作内存的变量副本中。use(使用):作用于工作内存的变量,将工作内存中的一个变量值传递给执行引擎。每当虚拟机遇到需要使用变量值的字节码指令时,就会执行这个操作。Assign(赋值):作用于工作内存的变量,将从执行引擎接收到的一个值赋值给工作内存的变量,每当虚拟机遇到为该变量赋值的字节码指令时执行此操作.Store(存储):作用于工作内存的变量,将工作内存中某个变量的值传送到主存中,以供后续的写操作。写(write):作用于主存中的一个变量,它将存储操作从工作内存中的一个变量的值转移到主存中的一个变量。对volatile修饰的变量进行操作时,需要满足以下规则:规则1:use只能在线程对变量执行的上一个动作为load时执行,否则load只能在下一个动作use时执行.线程与变量的读取、加载和使用动作相关联,必须连续出现。-----这样就保证了线程每次使用变量都需要从主存中获取最新的值,保证其他线程修改的变量能被本线程看到。规则二:store只有在线程对变量执行的前一个动作是assign时才能执行,反之亦然,assign只有在最后一个动作是store时才能执行。线程对变量的赋值、存储、写入动作是关联的,必须连续出现。-----这样就保证了每次修改变量时线程都会立即同步回主存,保证本线程修改的变量可以被其他线程看到。规则3:存在一个线程T,一个变量V,一个变量W。假设动作A是T对V的使用或赋值动作,P是根据规则2和3与A关联的读或写动作;actionB是T对W的使用或分配动作,Q是根据规则2和3与B相关联的读或写动作。如果A先和B在一起,那么P和Q先在一起。------这样可以保证被volatile修饰的变量不会被指令重排序优化,代码的执行顺序和程序的顺序一致。使用volatile关键字的特点1.volatile修饰的变量保证对所有线程可见。从上面的规则1和2我们可以看出volatile变量对所有线程都是立即可见的,不存在各个线程中的一致性问题。那么,我们是否可以断定volatile变量在并发操作下是线程安全的呢?这确实是一个很普遍的误解,写一个简单的例子:publicclassVolatileTestextendsThread{staticvolatileintincrease=0;staticAtomicIntegeraInteger=newAtomicInteger();//控制组staticvoidincreaseFun(){increase++;aInteger.incrementAndGet();}publicvoidrun(){inti=0;while(i<10000){increaseFun();我++;}}publicstaticvoidmain(String[]args){VolatileTestvt=newVolatileTest();intTHREAD_NUM=10;线程[]线程=新线程[THREAD_NUM];对于(inti=0;i2){Thread.yield();}System.out.println("volatile的值:"+increase);System.out.println("AtomicInteger的值:"+aInteger);}}这个程序我们运行10个线程,同时对volatile修饰变量进行10000次自增操作(AtomicInteger实现原子性,作为对照组)。如果volatile变量是并发安全的,运行结果应该是100000,但是多次运行后,每一次的结果都小于预期值。显然,上述说法是有问题的。volatile修饰的变量不保持原子性,所以在上面的例子中,使用volatile来保证线程安全是不可靠的。我们用Javap反编译这段代码,很容易看出不靠谱的原因:getstatic指令把increase的值拿到操作栈顶。此时由于volatile规则,该值是正确的。当执行到iconst_1和iadd指令时,增加的值很可能已经被其他线程增加了,此时栈顶的值就过期了。然后putstatic指令将过期值同步回主内存,从而产生更小的最终结果。volatile关键字只保证可见性,所以在以下情况下,需要使用锁来保证原子性:操作的结果取决于变量的当前值,并且有多个线程在修改变量的值。变量需要与其他状态变量一起参与不变约束。那么volatile这个特性的使用场景是什么呢?模式一:状态标志模式二:独立观察模式三:“volatilebean”模式模式四:低开销的“读写锁”策略具体场景:https://blog.csdn.net/vking_w...2。禁用指令重新排序优化。从上面规则3可以看出,volatile变量的第二个语义是禁止指令重排序。什么是指令重排序?简单来说,jvm会打乱代码中不依赖赋值的地方的执行顺序。由于一些规则,我们无法在单个线程中观察到中断现象(线程中存在串行语义),但在并发程序中,从另一个线程看另一个线程,运行是乱序的。一个很经典的指令重排序的例子:publicclassSingletonTest{privatevolatilestaticSingletonTestinstance=null;privateSingletonTest(){}publicstaticSingletonTestgetInstance(){if(instance==null){synchronized(SingletonTest.class){if(instance==null){instance=newSingletonTest();//非原子操作}}}returninstance;}}这就是单例模式中的“双重检查加锁模式”。我们看到instance装饰有volatile,因为instance=newSingletonTest();可以分解为:memory=allocate();//分配对象的内存空间ctorInstance(memory);//初始化对象instance=memory;//设置实例指向刚刚分配的内存地址2依赖1,但是操作3不依赖2,所以可能会出现1,3,2的顺序。当出现这种顺序时,虽然实例不为空,但对象可能没有被正确初始化,就会出现错误。总结并发、可见性、顺序、原子性三个特性,volatile通过在每次使用前立即将新值同步到主存并从主存刷新来保证可见性。通过禁止指令重新排序来保证排序。不能保证原子性。而我们知道synchronized关键字通过加锁和解锁操作来保证原子性。在解锁一个变量之前,该变量被同步回主存以确保可见性。同一时间只允许一个线程锁定一个变量。操作保证顺序。他的“通用”也间接导致了我们对synchronized关键字的滥用。控件越通用,对性能的影响就越大。虽然jvm不断通过各种方式优化synchronized关键字,但我们还是适时的记住了volatile关键字,哈哈哈哈。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.别在满屏的if/else中,试试策略模式,真的很好吃!!3.操!Java中xx≠null的新语法是什么?4、SpringBoot2.5发布,深色模式太炸了!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!