重庆的seo服务公司,优化大师安卓版,房地产市场调查的途径有哪些,微信官方免费下载文章目录 前言详解解决多线程下的问题 Happens-before原则总结as-if-serial语义happens-before的例子 前言
as-if-serial原则是Java内存模型中的一个重要概念。该规则规定#xff1a;不管怎么重排序#xff08;编译期间的重排序#xff0c;指令级并行的重排序as-if-serial原则是Java内存模型中的一个重要概念。该规则规定不管怎么重排序编译期间的重排序指令级并行的重排序内存系统的重排序等单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
为了获取更好的性能编译器和处理器常常会对指令做重排序但是他们必须遵守数据依赖性即在不改变单线程程序执行结果的前提下进行指令重排序。例如对于以下代码
int a 1; //语句1
int b 2; //语句2
int c a b; //语句3语句1和语句2没有数据依赖性可以重排序。但是语句3依赖于语句1和语句2所以它不能被重新排序到语句1或语句2之前。
然而这个原则只适用于单线程对于多线程就需要遵守happens-before原则确保线程间操作的有序性和可见性。
详解
as-if-serial原则是说不考虑并发编程的情况Java程序的执行结果应该与该程序在串行化环境中的执行结果一致。简单来说就是程序在执行过程中无论如何重新排序例如编译器的优化处理器的优化只要最终呈现出的执行结果与串行执行的结果一致那么这样的重排序是被允许的。
例如考虑以下代码
int a 1;
int b 2;
int c a b;依照as-if-serial原则虽然在执行过程中可能会将int b 2;语句移到int c a b;之后执行但是最终的执行结果c的值仍然与串行化执行的结果一致。
但在并发环境下as-if-serial原则可能会导致问题。例如
public class Counter {private int count 0;public void increment() {count;}public int getCount() {return count;}
}
在并发环境下如果有两个线程同时执行 increment() 方法由于 as-if-serial 原则编译器或处理器可能将 count 重排序为两个操作先读取 count 的值然后再写回 count1 的值。在两个线程并发执行的情况下可能第一个线程读取了 count 的值然后第二个线程也读取了 count 的值然后两个线程都将 count1 的值写回导致 count 的值只增加了 1而不是预期的 2。下面是一个更具体的例子来说明 as-if-serial 原则可能导致的问题java
public class Example {private int a 0;private int flag 0;public void writer() {a 1; //1flag 1; //2}public void reader() {if (flag 1) //3{int i a; //4}}
}在这个例子中假设有两个线程一个线程执行 writer() 方法另一个线程执行 reader() 方法。按照 as-if-serial 原则编译器或处理器可能会将 writer() 方法中的两行代码的顺序交换即先执行 flag 1然后再执行 a 1。如果这样的重排序发生reader() 方法可能会在 a 被赋值之前就读取到 flag 的值为 1然后读取到 a 的值为 0而不是预期的 1。
解决多线程下的问题
Java通过使用volatile关键字来解决这个问题。
当一个变量被volatile修饰后它将具备两种特性 可见性Visibility: 当一个线程修改了一个volatile变量的值新值对于其他线程来说是可以立即得知的。 禁止指令重排序优化普通的变量仅仅会满足1而被volatile修饰过的变量由于禁止指令重排序优化可以满足2。
这两种特性使得volatile变量在并发编程中非常有用。
例如在上面的例子中如果flag变量被声明为volatile那么两个线程看到的flag永远都是最新的如果writer线程更改了flag的值reader线程立刻就能看到这就解决了可见性问题。同时对一个volatile变量的任何写操作都会立即刷新到主存因此在写操作后的任何读操作都会看到这个新值。
volatile关键字还有一个额外的特性就是禁止指令重排序。编译器在执行优化时可能会重新排序代码的执行顺序。当flag变量被volatile关键字修饰后编译器就不会对这个变量前后的代码进行重排序这就保证了顺序性解决了重排序问题。
Happens-before原则
是用来判断数据是否存在竞争、线程之间的修改操作是否对其他线程可见的原则。
引入happens-before原则的原因主要有两个 解决可见性问题在并发编程中由于线程切换、编译器优化等原因一个线程对共享变量的修改不一定立即对其他线程可见这就导致了可见性问题。通过Happens-before原则我们可以清楚地知道哪些操作对其他线程可见。 解决有序性问题在并发编程中由于指令重排序代码的执行顺序可能会与我们编写的顺序不同。通过Happens-before原则我们可以明确的知道操作的前后顺序。
Happens-before原则包括以下几种规则 程序次序规则Program order rule一个线程中的每个操作happens-before于该线程中的任意后续操作。 监视器锁规则Monitor lock rule对一个锁的解锁happens-before于随后对这个锁的加锁。 volatile变量规则对一个volatile域的写happens-before于任意后续对这个volitile域的读。 传递性如果A happens-before B且B happens-before C那么A happens-before C。 start()规则如果线程A执行操作ThreadB.start()启动线程B那么A线程的ThreadB.start()操作 happens-before于线程B中的任意操作。 join()规则如果线程A执行操作ThreadB.join()并成功返回那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
例如假设有两个线程A和B线程A写入一个volatile变量然后线程B读取这个变量。那么线程A的写入操作happens-before线程B的读取操作线程B可以看到线程A的写入。
再例如假设一个线程先解锁一个对象然后另一个线程锁定这个对象。那么第一个线程的解锁操作happens-before第二个线程的加锁操作第二个线程可以看到第一个线程解锁前的所有操作。
总结
as-if-serial语义
这个程序中尽管编译器或处理器可能会重排序代码但是从程序的行为上看它就如同按照源代码的顺序串行执行的这就是as-if-serial语义。
public class AsIfSerial {private static int x 0;private static int y 0;public static void main(String[] args) {x 1;y 2;int a x;int b y;System.out.println(a a , b b);}
}happens-before的例子
在这个程序中线程A的flag true操作happens-before线程B的if(flag)操作因为它们之间有volatile变量规则和join规则。所以线程B可以看到线程A将flag设置为true。
public class HappensBefore {private static volatile boolean flag false;public static void main(String[] args) throws InterruptedException {Thread threadA new Thread(() - {flag true;});Thread threadB new Thread(() - {if (flag) {System.out.println(ThreadB sees flag true);}});threadA.start();threadA.join();threadB.start();threadB.join();}
}as-if-serial是一种程序优化原则它允许编译器和处理器对程序进行各种优化包括重新排序指令等但优化后的程序必须与按照程序源代码顺序执行的结果一致。这样可以保证程序的正确性又可以提高程序的运行效率。
happens-before则是一种描述多线程程序中两个或多个操作之间可能存在的偏序关系。如果操作A happens-before操作B那么A的结果对B是可见的即B可以看到A的效果。happens-before关系可以保证多线程程序的正确同步。