海外购物网站排名,网络营销外包平台,做问卷的网站有哪些,模板网站搭建Java的volatile关键字用于标记一个Java变量为“在主存中存储”。更确切的说#xff0c;对volatile变量的读取会从计算机的主存中读取#xff0c;而不是从CPU缓存中读取#xff0c;对volatile变量的写入会写入到主存中#xff0c;而不只是写入到CPU缓存。实际上#xff0c;…Java的volatile关键字用于标记一个Java变量为“在主存中存储”。更确切的说对volatile变量的读取会从计算机的主存中读取而不是从CPU缓存中读取对volatile变量的写入会写入到主存中而不只是写入到CPU缓存。实际上从Java5开始volatile关键字不只是保证了volatile变量在主存中写入和读取我回在后面的部分做相关的解释。变量可见性问题Java的volatile关键字保证了多个线程对变量值变化的可见性。这听起来有点抽象让我来详细解释。在一个多线程的程序中当多个线程操作非volatile变量时出于性能原因每个线程会从主存中拷贝一份变量副本到一个CPU缓存中。如果你的计算机有多于一个CPU每个线程可能会在不同的CPU中运行。这意味着每个简称拷贝变量到不同CPU的缓存中如下图对于非volatile变量并没有保证何时JVM从主存中读取数据到CPU缓存或者从CPU缓存中写出数据到主存。这会导致一些问题。想象一种情况多于一个线程访问一个共享对象这个共享对象包含一个计数变量如下声明public class ShareObject {public int counter 0;}考虑只有一个线程Thread1增加counter这个变量的值但是Tread1和Thread2可能有时会读取counter变量。如果counter变量没有被声明为volatile就不能保证何时这个变量的值会从CPU缓存写回主存这意味着在CPU缓存中的counter变量的值可能和主存中的不一样。如下图所示线程没有看到一个变量最新更新的值的原因是这个变量还没有被一个线程写回到主存这被称为“可见性”问题。一个线程对变量的更新对其他线程不可见。Java的volatile可见性保证Java的volatile关键字想要解决变量可见性问题。通过声明counter变量为volatile所有对counter变量的写入都回立即写回到主存同时所有对counter变量也都会从主存中读取。西面的代码展示了如何把counter变量声明为volatilepublic class SharedObject {public volatile int counter 0;}声明一个变量为volatile保证了对变量的写入对其他线程的可见性。在上面的场景中一个线程(T1)修改了counter变量的值另一个线程(T2)读取counter变量(但是不修改它)声明counter变量为volatile足以保证对counter变量的写入对T2可见。但是如果T1和T2都去增加counter变量的只name声明counter变量为volatile是不够的后面会说明。全volatile可见性保证实际上Java的volatile的可见性保证不止volatile变量本身。可见性保证如下如果线程A写一个volatile变量线程B随后读取这个volatile变量那么在写这个volatile变量之前对线程A可见的所有变量在线程B读取这个volatile变量之后对线程B也可见。如果线程A读取一个volatile变量那么当A读取这个volatile变量时所有对线程A可见的变量也可以从主存中再次读取。我用下面的代码来说明public class MyClass {private int years;private int months;private volatile int days;public void update(int years, int months, int days) {this.years years;this.months months;this.days days;}}update()方法写入三个变量只有days变量是volatile的。全volatile可见性保证的意思是当一个值写入到days变量则所有对当前线程可见的变量也会都写入到主存也就是当一个值写入到days变量则years和months的只也被写入到主存。当读取yearsmonths和days的值可以这样做public class MyClass {private int years;private int months;private volatile int days;public int totalDays() {int total this.days;total months * 30;total years * 365;return total;}public void update(int years, int months, int days) {this.years years;this.months months;this.days days;}}需要注意的是totalDays()方法起始于读取days的值到total变量中。当读取days的值时months和years的值也被读取到主存。因此可以保证你看到的是daysmonths和years的最新的值前提是保证上面的读取顺序。指令重排序挑战出于性能的考量JVM和CPU允许对程序中的指令进行重排序只要指令的语义不变。例如下面的指令int a 1;int b 2;a;b;这些指令可以按照下面的顺序重排并不会丢失程序的语义int a 1;a;int b 2;b;但是指令重排序对于其中一个变量是volatile变量这种情况是有挑战的。让我们看一下MyClass这个类public class MyClass {private int years;private int months;private volatile int days;public void update(int years, int months, int days) {this.years years;this.months months;this.days days;}}一旦update()方法对days变量写入一个值years和months新写入的只也刷入到主存但是如果有JVM指令重排序像下面这样public void update(int years, int months, int days) {this.days days;this.months months;this.years years;}months和years的只在days变量修改的情况下依然会写入到主存但是这时将years和days变量值刷入主存这件事发生在对months和years写入新值之前则对years和days的更新对其他线程来说就不可见了。这下指令重排序就改变了程序的语义。Java有一个应对此问题的解决方案下面会讲到。Java的volatile的Happens-Before保证为了解决指令重排序的挑战Java的volatile关键字除了可见性保证之外给出了一个“happens-before”的保证。happens-before保证如下情况如果读取和写入其他非volatile变量发生在写入volatile变量之前(这种情况这些非volatile变量也会被刷入主存)则读取和写入这些变量不能被重排序为发生在写入这个volatile变量之后(禁止指令重排序)。在写入一个volatile变量之前的读取和写入非volatile变量被保证为“happen before”写入这个volatile变量。需要注意的是例如在写入一个volatile变量之后读写其他变量可以被重排序到写入这个volatile变量之前。从“之后”重排序到”之前“是允许的但是从”之前“重排序到”之后“是禁止的。如果读写其他非volatile变量发生在读取一个volatile变量之后(这种情况这些非volatile变量也会被刷到主存)则读写这些变量不能被重排序为发生在读取这个volatile变量之前。需要注意的是读取其他变量发生在读取一个volatile变量之前能够被重排序为发生在读取这个volatile变量之后。从”之前“重排序到“之后”是允许的但是从“之后”重排序到“之前”是被禁止的。上面的happens-before保障保证的volatile关键字的可见性是强制的。volatile不总是足够的尽管volatile关键字保证了所有对一个volatile变量的读取都是从主存中读取所有对volatile关键字的写入都是直接到主存但是仍有其他情况使得声明一个变量为volatile是不足够的。在前面解释的情况也就是只有Thread1写共享变量counter声明counter变量为volatile足以保证Thread2总是看到最新写入的值。实际上多线程都可以写一个共享的volatile变量并且仍然在主存中存储正确的值前提是写入变量的新值不依赖于它之前的值。也就是说如果一个线程写入一个值到共享的volatile变量不需要先去读它的值去产出下一个值。只要一个线程需要首先读取一个volatile变量的值基于这个值生成一个新值则一个volatile关键字不足以保证正确的可见性。在读取volatile变量然后写入新值的短暂的间隙会产生竞态条件(race condition)这时多个线程可能读取到相同的volatile变量的值生成这个变量的新值当将新值写回主存时会覆盖彼此的值。多线程增加相同计数器的值就是这种情况导致一个volatile声明不足够。下面详细解释这种情况。想象如果Thread1读取一个值为0的共享的counter变量到它的CPU缓存增加1并且不将这个改变的值写回主存。Thread2然后从主存中读取相同的值仍为0counter变量到它的CPU缓存。Thread2也为它增加1也不写回主存。这种情况如下图所示Thread1和Thread2此时实际上已经不同步了。共享变量counter的值应该为2但是每个线程在CPU缓存中的这个变量的值都为1在主存中的值仍为0这就乱了尽管这两个线程最终会将值写回主存中的共享变量这个值也是不正确的。何时volatile是足够的正如前面所说如果两个线程都去读写同一个共享变量只对这个共享变量使用volatile关键字是不够的。你需要使用一个synchronized关键字去保证读写相同变量是原子的。读写一个volatile变量不会阻塞线程的读写。作为synchronized块替代方法你可以使用java.util.concurrent包中的众多原子数据类型。比如AtomicLong或者AtomicReference或其他的类型。只有一个线程读写一个volatile变量值其他线程只读取变量则这些读线程能够保证看到写入这个volatile变量的最新值如果不声明为volatile则这种情况不能保证。volatile的性能考量读写volatile变量会导致变量被读写到主存。读写主存比访问CPU缓存开销更大。访问volatile变量也会禁止指令重排序而指令重排序是一个正正常的性能优化技术。因此你应该只在真正需要保证变量可见性的时候使用volatile变量。