持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天
1.Volatile
1、保证可见性
public class JMMDemo {
// 不加 volatile 程序就会死循环
// 加 volatile 保证了可见性
private volatile static int num =0 ;
public static void main(String[] args) {
new Thread(()->{ //线程1 对内存的变化不知道
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1 ;
System.out.println(num);
}
}
不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
//volatile 不保证原子性
public class JMMDemo02 {
private static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" " +num);
}
}
如果不加lock和synchronized ,怎么样保证原子性?
解决方法:使用原子类,解决原子性问题
//volatile 不保证原子性
public class JMMDemo02 {
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement(); //底层是CAS保证的原子性
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" " +num);
}
}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
可能造成的影响结果:前提:a b x y这四个值 默认都是0
| 线程A | 线程B |
|---|---|
| x=a | y=b |
| b=1 | a=2 |
正常的结果: x = 0; y =0;
| 线程A | 线程B |
|---|---|
| x=a | y=b |
| b=1 | a=2 |
可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
总结
- volatile可以保证可见性;
- 不能保证原子性
- 由于内存屏障,可以保证避免指令重排的现象产生
面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式