Volatile
保证可见性
public class JMMDemo {
//加了volatile,可以保证可见性
private static volatile int num=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=1; //主内存中的值一旦发生变化如何通知另一个线程,另一个线程不知道主内存的值被修改过了 ==>Volatile
System.out.println(num);
}
}
不保证原子性
原子性:不可分割
线程A在执行任务时不能被打扰或分割
public class VDemo02 {
//不保证原子性 => main 19209
private static volatile int num=0;
public static void main(String[] args) {
//理论结果应为20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//main线程 以及 GC线程
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
//增加synchronized关键字=> main 20000
public static void add(){
num++;
}
}
如果不增加synchronized和lock锁如何保证原子性?
num++根本不是一个原子性操作
反编译源码
public static void add();
Code:
0: getstatic #15 //获得这个值
3: iconst_1 //获取num静态变量
4: iadd //+1
5: putstatic #15 //写回这个值
8: return
使用原子类来解决原子性问题
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力
public class VDemo02 {
//不保证原子性 => main 19209
private static volatile AtomicInteger num=new AtomicInteger(0);
public static void main(String[] args) {
//理论结果应为20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//main线程 以及 GC线程
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
//增加synchronized关键字=> main 20000
public static void add(){
num.getAndIncrement(); //加一方法:底层为CAS 效率极高 直接在内存中修改值,unsafe类类似于c语言中的指针
}
}
禁止指令重排
你写的程序,计算机并不是按你写的顺序执行的
源代码 => 编译器优化的重排 => 指令并行也可能会重排 => 内存系统也会重排 => 执行
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
期望顺序:1 2 3 4
但是 2 1 3 4,1 3 2 4 也可以得出相同结果
不可能会是 4 1 2 3,处理器在进行指令重排时会考虑数据之间的依赖性
可能造成影响的结果 a b x y 默认都为0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果 x=0 y=0,但是可能由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果:x=2;y=1
只要加了Volatile可以避免指令重排
内存屏障,cpu指令,作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这些特性可以保证volatile的可见性)
单例模式使用最多