JMM、volatile
JMM

JMM规定:
按顺序执行read--load,且不可单独出现。
按顺序执行store-- write,且不可单独出现。
有assign操作后,变量改变后,需要同步到主内存中。
新变量必须诞生在主内存中,不可使用未load过的变量。
一个变量同一时刻只允许一个线程lock它。且一个lock就要一个unlock解开。
对变量lock操作,将会清空所有本地内存中此变量。
unlock一个变量前必须有store 和 write操作。
volatile
线程之间的可见性
线程A读取不到线程B刷新到主存的数据flag=fale,造成死循环
import java.util.concurrent.TimeUnit; public class VolatileAccessibleTest { private static boolean flag = true; public static void main(String[] args) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"即将进入while循环"); while (flag){ } },"A").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } flag = false; System.out.println(Thread.currentThread().getName()+"设置flag=false,停止线程A"); },"B").start(); } }

可见性
public class VolatileAccessibleTest { private static volatile boolean flag = true; public static void main(String[] args) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"即将进入while循环"); while (flag){ } },"A").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } flag = false; System.out.println(Thread.currentThread().getName()+"设置flag=false,停止线程A"); },"B").start(); } }

不保障线程的原子性
import java.util.concurrent.TimeUnit; public class VolatileNoAtomicTest { private static volatile int num = 0; public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 20; i++) { new Thread(()->{ for (int i1 = 0; i1 < 1000; i1++) { add(); } }).start(); } TimeUnit.SECONDS.sleep(2); System.out.println(num); } private static void add() { num++; } }

AtomicXXX原子类、synchronized、lock可以保障原子性
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class VolatileNoAtomicTest { private static AtomicInteger num = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 20; i++) { new Thread(()->{ for (int i1 = 0; i1 < 1000; i1++) { add(); } }).start(); } TimeUnit.SECONDS.sleep(2); System.out.println(num); } private static void add() { num.getAndIncrement(); } }
禁止指令重排

double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C
指令重排:ABC正常、BAC正常、CAB不正常。
处理器在进行指令重排的时候,考虑数据之间的依赖性。
volitale避免指令重排,保证特定操作的执行顺序,可以保证某些变量的内存可见性
happens-before规则
happens-before表示的是前一个操作的结果对于后续操作是可见的,它是一种表达多个线程之间对于内存的可见性。所以我们可以认为在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程。
内存屏障
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障。

浙公网安备 33010602011771号