volatile的作用
1.保证线程可见性
会将线程中变量的副本值改变后立刻写回到主内存
但是其它线程什么时候会去检查该变量是否发生变化,volatile控制不了
本质上是使用了CPU 的高速缓存一致性协议 在inter CPU下使用的是 MESI协议
2.禁止指令重排序
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
User user = new User();
}
}
User user = new User();
实际上有三步:
1.申请内存
2.初始化
3.赋值
加了volatile 就可以避免(在多核CPU下)产生的指令重排序
使用场景举例:
单例模式:饿汉式DLC(double lock check)
public class Singleton {
private static volatile Singleton INSTANCE;
private Singleton(){}
public static Singleton getInstance(){
if (INSTANCE == null){
synchronized (Singleton.class){
if (INSTANCE == null){
INSTANCE = new Singleton(); //实际上有三步: 申请内存 初始化 赋值
}
}
}
return INSTANCE;
}
}
在INSTANCE = new Singleton()时,如果不加volatile,那么有可能发生指令重排序.
即 申请完内存后 就赋值给INSTANCE,此时INSTANCE !=null,但还未赋值
此时当另一个线程进来时if条件成立,就会重新new Singleton();
所以不安全.但发生效率极低,除非超高并发.
volatile不适用的场景
本质上是因为volatile只能保证当前线程将改变后的值写回到主内存,但无法控制其它线程什么时候去读这个改变后的值,不能保证原子性
public class VolatileNotSync {
volatile int count = 0; //只能保证主内存可见 但其它线程不一定能第一时间能读到这个新值
public /*synchronized*/ void m(){
for (int i=0;i<10000;i++){
count ++; //非原子操作 所以必须加synchronized
}
}
public static void main(String[] args) {
VolatileNotSync volatileNotSync = new VolatileNotSync();
List<Thread> threads = new ArrayList<>();
for (int i=0;i<10;i++){
threads.add(new Thread(volatileNotSync::m,"thread-"+ i));
}
threads.forEach((thread)->thread.start());
threads.forEach((thread)-> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(volatileNotSync.count); //不加synchronized 最后结果永远得不到100000
}
}