我在线程上遇到了这个小问题。
int x = 0;
add() {
x=x+1;
}
如果我们在多个线程中运行它,比如 4 个线程,那么每次 x=4 的最终值或者它可能是 1、2、3 或 4。
谢谢
PS可以说添加的原子操作是这样的,
LOAD A x
ADD A 1
LOAD x A
那么最终结果将是 4。我是对的还是我错了?
我在线程上遇到了这个小问题。
int x = 0;
add() {
x=x+1;
}
如果我们在多个线程中运行它,比如 4 个线程,那么每次 x=4 的最终值或者它可能是 1、2、3 或 4。
谢谢
PS可以说添加的原子操作是这样的,
LOAD A x
ADD A 1
LOAD x A
那么最终结果将是 4。我是对的还是我错了?
这是数据竞争的经典例子。
现在,让我们仔细看看 add() 的作用:
add()
{
x = x + 1;
}
这转化为:
现在,在我们进一步解释这一点之前,您有一个称为上下文切换的东西,它是您的操作系统在不同线程和进程之间分配处理器时间的过程。此过程通常为您的线程提供有限的处理器时间(在 Windows 上约为 40 毫秒),然后中断该工作,复制处理器在其寄存器中的所有内容(并因此保留其状态)并切换到下一个任务。这称为循环任务调度。
您无法控制您的处理何时会被中断并转移到另一个线程。
现在想象你有两个线程在做同样的事情:
1. Give me the most recent value of X and store it in my private workspace
2. Add 1 to that value that is stored in my private workspace
3. Copy what I have in my workspace to the memory that I copied from (that is globally accessible).
并且 X 在它们中的任何一个运行之前等于 1。
第一个线程可能会执行第一条指令,并将其工作时最近的 X 值存储在其私有工作区中 - 1。然后发生上下文切换,操作系统中断您的线程并将控制权交给队列中的下一个任务,恰好是第二个线程。第二个线程还读取等于 1 的 X 的值。
第二个线程设法运行完成 - 它将“下载”的值加 1 并“上传”计算的值。
操作系统再次强制进行上下文切换。
现在第一个线程在它被中断的地方继续执行。它仍然会认为最近的值是 1,它会将该值加一并将其计算结果保存到该内存区域。这就是数据竞争的发生方式。您期望最终结果是 3,但它是 2。
您的代码在两个级别上被破坏:
happens-before
强加关系;解决1.可以加volatile
修饰符。这仍然会使操作成为非原子操作。为了确保原子性,您将(最好)使用AtomicInteger
或synchronized
(涉及锁定,不是首选)。
就目前而言,如果从不参与递增的线程读取结果,则结果可以是0 到 4 之间的任何数字。
多线程应用程序是并发的(这是重点)。
t1: LOAD A1 x
t2: LOAD A2 x
t3: LOAD A3 x
t4: LOAD A4 x
t1: ADD A1 1
t2: ADD A2 1
t3: ADD A3 1
t4: ADD A4 1
t1: STORE x A1
t2: STORE x A2
t3: STORE x A3
t4: STORE x A4
A1、A2、A3、A4 是本地寄存器。
结果是1
,但也可能是2
,3
或4
。如果您有另一个线程,由于可见性问题,它可能会看到旧值并查看0