volatile用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排。
1)可见性。当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1、线程2...线程n能够立即读取到线程1修改后的值。每个线程都有自己的缓存空间,对变量的操作时在缓存中进行的,之后再将修改的写到缓存中。当变量经过volatile修饰后,对此变量进行写操作时,会将当前处理器的被修改的变量强制更新到主存中,同时将其它处理器中的该变量值置为无效,需要重新到主存中读取。
2)有序性。即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
3)不具有原子性。在java中,只有对基本数据类型的读取和赋值是原子操作。而对于i++等运算操作都不是原子操作,因为他们都进行了多次原子操作。比如先读取j,再将j的值加给i,这就进行了两次操作。
例如:一个变量i被volatile修饰,有两个线程都要对i进行自加即i++,而i++的过程可以分为三步,首先读取i的值,然后对i的值加1,最后将得到的结果写到缓存和主存中。
线程A首先得到了i的值20,还没修改,发生阻塞,此时线程B开始,也读取到了i的值20,之后对其进行自加操作,得到21,将值写道缓存中,由于volatile的修饰,该值会刷到主存中。而由可见性可得,这个主存的值可以被其他线程可见。
此时问题就出现了,线程A已经读取到了i的值为20,也就是说读取的这个原子操作已经结束了,所以这个可见性已经晚了,当线程A阻塞结束后,继续对该变量自加得到101,再将值写到缓存并刷到内存。由此可见即使volatile具有可见性,也不能保证对它修饰的变量具有原子性。
1)适用于对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量。
2)适用于读多写少的场景。可用作状态标志。
3)JDK中volatie应用:JDK中ConcurrentHashMap的Entry的value和next被声明为volatile,AtomicLong中的value被声明为volatile。AtomicLong通过CAS原理(也可以理解为乐观锁)保证了原子性。
sychronized具有原子性、可见性以及有序性。volatile不会让线程阻塞,响应速度比synchronized高。
原文:https://www.cnblogs.com/yinghang/p/volatile.html