多线程学习:volatile的作用
一:保证线程可见性
多线程的情况下,有volatile关键字:变量值发生变化,其他线程能实时知道。
多线程读取同一个对象时,每个线程会copy这个对象当前的值存起来用。
1: Demo栗子–有无volatile的区别
public class Demo {
public volatile boolean running = true;
public static void main(String[] args) {
Demo d = new Demo();
new Thread(d::m1).start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
d.running = false;
}
public void m1() {
System.out.println("开始m1方法");
while (running) {
}
System.out.println("结束m1方法");
}
}
没有volatile关键字时,m1方法不会结束。
存在volatile关键字时,m1方法3秒后会结束。
二:禁止指令重排序
1: new 对象的三部曲
new 一个对象时,分成三步骤,如:String s = new String(“123”);
a: 给对象申请推内存 (int 类型-初始值为0; 引用类型–初始值为null)
b: 成员变量初始化 (将123赋值给s)
c: 栈指向堆内存 (在此之前s对象为null)
2: 指令重排序
现在的cpu是并行执行指令的,可能出现,a完成之后,先执行c步骤,再执行b步骤。
多线程情况下,a、c完成后,b未执行时,拿到的s对象的值为null。
加了volatile之后,能保证a、b、c顺序执行。
三:不能保证原子性,不能替代syc
public class Demo1 {
volatile int count = 0;
void m() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
Demo1 d = new Demo1();
List<Thread> threads = new ArrayList<>();
//创建10个线程去调用m方法
for (int i = 0; i < 10; i++) {
threads.add(new Thread(d::m));
}
threads.forEach((o) ->
o.start()
);
threads.forEach(o->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(d.count);
}
}
打印出来的结果不是100000。
为什么呢?
多线程读取同一个对象时,每个线程会copy这个对象当前的值存起来用。
当一个线程把count变成1,
第二、三个线程读取count 为1,进行了++运算,还没有写回内存
第二个线程执行完,写回去count = 2
此时,第三个线程更新到count = 2,但是第三个线程本身的逻辑执行完了,写回去count 还是 2