(1)原子性问题:强调操作的完整性,不可被打断性。举例:将共享变量sum进行自增操作,汇编层面的操作:
- 获取当前sum变量的值,并且放入栈顶
- 将常量1放入栈顶
- 将当前栈顶的两个值(sum的值和1)相加,并把结果放入栈顶。
- 把栈顶的结果再赋值给sum变量。
ps:++操作不是原子操作,分为的4个操作之间是可以发生线程切换的,或者说是可以被其他线程中断的。
(2)可见性问题:强调一个线程的操作对另一个线程能否立刻可见。举例:
- 主存中有变量sum,初始值为0
- 线程A计划将sum加1,现将sum=0从主内存复制到自己的工作线程中,然后更新sum的值。线程A操作完成后其私有内存(工作内存)中的sum的值为1,然后线程A将更新的sum值回刷到主存的时间是不固定的。
- 在线程A没有回刷sum到主存前,刚好线程B同样从主存中读取sum。此时值为0,和线程A进行同样的操作,最后期盼的sum=2目标没有达成,最终sum=1
ps:线程B没有将sum变成2的原因是:线程A的修改还在其工作内存中,对线程B不可见,因为线程A的修改还没有刷入主存。这就是典型的数据不可见问题。解决方案是:使用关键字volatile修饰共享变量。
(3)有序性问题:强调程序执行的顺序和代码的先后顺序不同。举例:
线程A和B执行的代码为:
x=0;y=0;a=0;b=0;
ThreadA:{
a = 1;//①
x = b;//②
}
ThreadB:{
b = 1;//③
y = a;//④
}
执行顺序可能为:①②③④:x=0,y=1;③④①②:x=1,y=0;①③②④:x=1,y=1;①③④②:x=1,y=1;③①②④:x=1,y=1;③①④②:x=1,y=1;均是正常的,但是发但并发出现的结果里面为x=0,y=0就不正常了;发生该结果的可能的执行顺序是④②①③、④②③①、②④①③、②④③①,后面的四种执行顺序有一特点:就是在线程A中存在倒序即②的执行顺序在①之前或者是线程B中存在倒序即④的顺序在③之前或者线程A、B都存在“倒序”的情况。
ps:指令重排序是CPU为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序同代码的先后顺序一致,但是会保证程序最终的执行结果和代码顺序执行的结果是一致的。反观上述例子,但对线程A来说,“倒序”并不会改变A原本的结果。同理,但对B来说,“倒序”也不会改变B原本的结果。因此有可能发生了指令重排导致的。