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种原因。