(1)原子性问题:强调操作的完整性,不可被打断性。举例:将共享变量sum进行自增操作,汇编层面的操作:

  1. 获取当前sum变量的值,并且放入栈顶
  2. 将常量1放入栈顶
  3. 将当前栈顶的两个值(sum的值和1)相加,并把结果放入栈顶。
  4. 把栈顶的结果再赋值给sum变量。

ps:++操作不是原子操作,分为的4个操作之间是可以发生线程切换的,或者说是可以被其他线程中断的。

(2)可见性问题:强调一个线程的操作对另一个线程能否立刻可见。举例:

  1. 主存中有变量sum,初始值为0
  2. 线程A计划将sum加1,现将sum=0从主内存复制到自己的工作线程中,然后更新sum的值。线程A操作完成后其私有内存(工作内存)中的sum的值为1,然后线程A将更新的sum值回刷到主存的时间是不固定的。
  3. 在线程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原本的结果。因此有可能发生了指令重排导致的。