volatile
1、保证可见性
2、不保证原子性
3、禁止指令重排
1、保证可见性
package com.cc.tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//不加 volatile 程序就会死循环
//加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while(num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
package com.cc.tvolatile;
//volatile不保证原子性
public class VDemo02 {
//volatile不保证原子性
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上num结果应该为2万
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {//main.gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+ " " + num);
}
}
num的结果理论上应该是2万,但是你运行发现每次结果都不固定,大部分时候都不是两万。
如下,我截取了一个结果。
//输出结果
main 18779
可以加 lock 和 synchronized,来保证原子性。
如下:
package com.cc.tvolatile;
//volatile不保证原子性
public class VDemo02 {
//volatile不保证原子性
private static int num = 0;
public static synchronized void add() {
num++;
}
public static void main(String[] args) {
//理论上num结果应该为2万
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {//main.gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+ " " + num);
}
}
如果不加 lock 和 synchronized,怎么样来保证原子性呢?
使用原子类,解决原子性问题。
package com.cc.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
//volatile不保证原子性
public class VDemo02 {
//volatile不保证原子性
//原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
//num++; //不是原子性的操作
num.getAndIncrement();
}
public static void main(String[] args) {
//理论上num结果应该为2万
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {//main.gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+ " " + num);
}
}
3、指令重排
什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的:1234 但是可能执行的时候回变成 2134 1324
可不可能是 4123!
可能造成影响的结果: 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指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!