回顾之前Java内存模型特色能够理解到该模型是围绕着并发过程中如何解决原子性、可见性和有序性这三个特色来建设的。
原子性:一个操作或多个操作要么全副执行实现且执行过程不被中断,要么就不执行。Java内存模型来间接保障的原子性变量操作包含read、load、assign、use、store和write这六个,如果利用场景须要更大范畴的原子性保障,Java内存模型还提供了lock和unlock操作来满足需要,比方synchronize关键字。在synchronize块之间的操作就具备原子性。
可见性:指当一个线程批改了共享变量的值,其它线程可能立刻得悉这个批改。Java内存模型是通过在变量批改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的形式来实现可见性的。
有序性:在本线程内,所有操作都是有序的,即依照代码先后顺序执行;如果在一个线程察看另一个线程,所有操作都是无序的,因为有“指令重排序”景象和“工作内存与主内存同步提早”景象。
理解上述并发解决三大个性之后,再看volatile关键字,该关键字满足可见性和有序性,所以在Java中能提供最轻量级的同步机制。
volatile可见性:volatile的规定保障了新值能立刻同步到主内存,以及每次应用前立刻从主内存刷新。因而volatile保障了多线程操作变量的可见性。
《2020最新Java根底精讲视频教程和学习路线!》
volatile有序性:应用volatile润饰的变量会禁止指令重排序,JVM中对不是原子性的操作会进行重排序的优化,只有不影响最终计算结果。例如:
// 线程1中{ ... obj= getObject(); // 步骤1 isRegister = true; // 步骤2 ...} // 线程2中{ ... if (isRegister) { // 步骤3,依赖步骤2的值 fun(obj); // 步骤4,依赖步骤1的值 } ...}
上述代码中,线程1中的代码步骤1和步骤2可能呈现重排序的状况,因为对线程1来说程序打乱不影响线程1本人的运算后果,然而对线程2来说,如果线程1中的步骤2先执行,最终就无奈失去正确的后果。
volatile润饰的变量,在读取或者写入的前后都会插入内存屏障来达到禁止重排序的成果,进而保障有序性。
volatile不能保障原子性,因而不能齐全达到线程平安成果,除非满足一下条件:
- 运算后果不依赖以后值,或者可能确保只有繁多的线程批改该变量的值。
- 变量不须要与其余状态变量独特参加不变束缚。
比方volatile润饰的变量count呈现count++、count+1等非原子操作,就无奈确保线程平安。
import java.io.*;class test { private static final int THREAD_COUNT = 20; public static volatile int race = 0; public static void increase () { race++; } public static void main (String[] args) throws java.lang.Exception { System.out.println("hi"); for (int i = 0; i < THREAD_COUNT; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 1000; j++) { increase(); } } }).start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println("race = "+ race); }}
运行后果:
hi
race = 19902
留神,测试的后果跟环境无关,有的测试环境可能后果呈现正确的状况,能够将测试数据改大一点。自己测试时,开始抉择10个线程,而后只累加10次,测试下来发现后果都是正确的~
另外大家可能会纳闷volatile不是保障变量的可见性了吗?一旦被批改会立刻同步到主内存中,确保其它线程都拿到最新数据。这里能够这样了解,比方线程1和线程2都拿到最新volatile润饰的变量count = 10,当count++时,因为不是原子操作,当线程1还在执行加加指令时,线程2曾经将count数据11更新到主存了,此时对于线程1来说,数据曾经过期了,等线程1执行完时,又将11同步到主存,后果导致两个线程都执行count++,然而最终后果却小于正常值。
链接地址:https://blog.csdn.net/yj_android_develop/article/details/111094154