1.抢占式执行随机调度
这里的意思就是,当两个线程同时启动的时候,两个线程会同时进行,并且是抢占式执行的。
而且是随机调度资源的。
如代码:
public class Deome4 {
public static void main(String[] args) {
Thread t1 = new Thread(()-> {
for(int i = 0; i < 100; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1线程");
}
});
Thread t2 = new Thread(()-> {
for(int i = 0; i < 100; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t2线程");
}
});
t1.start();
t2.start();
}
}
如打印结果:
以上我们可以看到,线程是同时进行的。
2.两个线程同时修改同一个变量
当两个线程同时修改一个变量时,会存在运算被覆盖的情况。
如代码:
public class Deome4 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()-> {
for(int i = 0; i < 10000; i++){
count++;
}
});
Thread t2 = new Thread(()-> {
for(int i = 0; i < 10000; i++){
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
如图:
上述代码,如果按照步骤应该是 20000, 但这里结果不是,这里就存在了问题。
主要是因为 在对 count++的时候,共分为3个步骤,因为线程的抢占式执行,导致有的值被覆盖掉了,所以不一样。
3.修改操作不是原子的
咱们就拿 count++ 为例,
count++ 分为3个步骤:
load:把内存中的数据读取到寄存器中。
add:把寄存器中的数据加1。
save:把寄存器中的值写入到内存中。
这里的原子性就是指不可拆分。
因为这里不是原子性的,着里线程随机调度,就会中出现错误。
4.内存可见性问题
什么是内存可见性问题呢?
如代码:
public class Deome4 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()-> {
while(count == 0){
;
}
System.out.println("t1 循环结束");
});
Thread t2 = new Thread(()-> {
Scanner sc = new Scanner(System.in);
count = sc.nextInt();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
如图:
按道理来说我如果这里输入的是1,那么代码应该就结束的,但是这里并没有结束,此时就是内存可性问题。
这里是因为,我们的load的执行速度相比于cmp慢了太多了。此时JVM就做出来一个非常大胆的决定--不再真正的去重复load了,因为判定好像没人去修改count的值,所以干脆就只获取一次就好了,此时就出现了前面运行的情况了。
以上是线程安全问题的4种原因。