给我一个场景,在多线程发生时可能会发生这样的输出
Give me a scenario where such an output could happen when multi-threading is happening
只是想了解线程和竞争条件以及它们如何影响预期输出。在下面的代码中,我曾经有一个以
“2 Thread-1”然后是“1 Thread-0”....怎么会出现这样的输出?我的理解是这样的:
第 1 步:假设线程 0 启动,它递增计数器到 1,
Step2:在打印之前,线程1将其递增为2并打印,
第 3 步:线程 0 打印本应为 2 但实际为 1 的计数器。
当线程 1 已经将计数器递增到 2 时,线程 0 如何将计数器打印为 1?
P.S: 我知道synchronized key可以处理这样的竞争条件,但我只是想在之前完成一些概念。
public class Counter {
static int count=0;
public void add(int value) {
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
}
}
public class CounterThread extends Thread {
Counter counter;
public CounterThread(Counter c) {
counter=c;
}
public void run() {
for(int i=0;i<5;i++) {
counter.add(1);
}
}
}
public class Main {
public static void main(String args[]) {
Counter counter= new Counter();
Thread t1= new CounterThread(counter);
Thread t2= new CounterThread(counter);
t1.start();
t2.start();
}
}
How could Thread 0 print counter as 1 when Thread 1 already incremented it to 2?
这两行中发生的事情比我们看到的要多得多:
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
首先,编译器对线程一无所知。它的工作是发出在单线程中执行时将实现相同 最终结果 的代码。也就是说,当一切都说完之后,计数必须递增,并且必须打印消息。
编译器有很大的自由度来重新排序操作,并将值存储在临时寄存器中,以确保以最有效的方式获得正确的最终结果。因此,例如,表达式 count+" "+...
中的 count
不一定会导致编译器获取全局 count
变量的最新值。事实上,它可能 而不是 从全局变量中获取,因为它知道 +
操作的结果仍然位于 CPU 寄存器中。而且,由于它不承认可能存在其他线程,因此它 知道 寄存器中的值不可能与它在之后存储到全局变量中的值有任何不同做 +
.
其次,允许硬件本身在临时位置存储值并重新排序操作以提高效率,并且也允许假设没有其他线程。因此,即使编译器发出的代码表示实际从全局变量中获取或存储到全局变量而不是从寄存器中获取或存储,硬件也不一定存储到内存中的实际地址或从中获取实际地址。
假设您的代码示例是 Java 代码,那么当您适当使用 synchronized
块时,所有这些都会改变。例如,如果您将 synchronized
添加到 add
方法的声明中:
public synchronized void add(int value) {
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
}
这会强制编译器承认其他线程的存在,并且编译器将发出指令强制硬件也承认其他线程。
通过将 synchronized
添加到 add
方法,您强制硬件在方法入口处传递全局变量的实际值,您强制它实际写入全局变量对方法计时 returns,并且您可以防止多个线程同时在该方法中。
只是想了解线程和竞争条件以及它们如何影响预期输出。在下面的代码中,我曾经有一个以 “2 Thread-1”然后是“1 Thread-0”....怎么会出现这样的输出?我的理解是这样的:
第 1 步:假设线程 0 启动,它递增计数器到 1,
Step2:在打印之前,线程1将其递增为2并打印,
第 3 步:线程 0 打印本应为 2 但实际为 1 的计数器。
当线程 1 已经将计数器递增到 2 时,线程 0 如何将计数器打印为 1?
P.S: 我知道synchronized key可以处理这样的竞争条件,但我只是想在之前完成一些概念。
public class Counter {
static int count=0;
public void add(int value) {
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
}
}
public class CounterThread extends Thread {
Counter counter;
public CounterThread(Counter c) {
counter=c;
}
public void run() {
for(int i=0;i<5;i++) {
counter.add(1);
}
}
}
public class Main {
public static void main(String args[]) {
Counter counter= new Counter();
Thread t1= new CounterThread(counter);
Thread t2= new CounterThread(counter);
t1.start();
t2.start();
}
}
How could Thread 0 print counter as 1 when Thread 1 already incremented it to 2?
这两行中发生的事情比我们看到的要多得多:
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
首先,编译器对线程一无所知。它的工作是发出在单线程中执行时将实现相同 最终结果 的代码。也就是说,当一切都说完之后,计数必须递增,并且必须打印消息。
编译器有很大的自由度来重新排序操作,并将值存储在临时寄存器中,以确保以最有效的方式获得正确的最终结果。因此,例如,表达式 count+" "+...
中的 count
不一定会导致编译器获取全局 count
变量的最新值。事实上,它可能 而不是 从全局变量中获取,因为它知道 +
操作的结果仍然位于 CPU 寄存器中。而且,由于它不承认可能存在其他线程,因此它 知道 寄存器中的值不可能与它在之后存储到全局变量中的值有任何不同做 +
.
其次,允许硬件本身在临时位置存储值并重新排序操作以提高效率,并且也允许假设没有其他线程。因此,即使编译器发出的代码表示实际从全局变量中获取或存储到全局变量而不是从寄存器中获取或存储,硬件也不一定存储到内存中的实际地址或从中获取实际地址。
假设您的代码示例是 Java 代码,那么当您适当使用 synchronized
块时,所有这些都会改变。例如,如果您将 synchronized
添加到 add
方法的声明中:
public synchronized void add(int value) {
count=count+value;
System.out.println(count+" "+ Thread.currentThread().getName());
}
这会强制编译器承认其他线程的存在,并且编译器将发出指令强制硬件也承认其他线程。
通过将 synchronized
添加到 add
方法,您强制硬件在方法入口处传递全局变量的实际值,您强制它实际写入全局变量对方法计时 returns,并且您可以防止多个线程同时在该方法中。