Java 并发变量修改:无法完全理解此示例
我需要一些帮助来完全理解运行此代码时发生的情况Java 并发变量修改:无法完全理解此示例,java,multithreading,concurrency,Java,Multithreading,Concurrency,我需要一些帮助来完全理解运行此代码时发生的情况 public class Main extends Thread { private static int x; public static void main(String[] args) { Thread th1 = new Main("A"); Thread th2 = new Main("B"); th1.start(); th2.start();
public class Main extends Thread {
private static int x;
public static void main(String[] args) {
Thread th1 = new Main("A");
Thread th2 = new Main("B");
th1.start();
th2.start();
}
public Main(String n) {
super(n);
}
public void run() {
while(x<4) { //1
x++; //2
System.out.print(Thread.currentThread().getName()+x+" "); //3
}
}
}
我知道线程A
和B
都递增x
,然后B
循环递增和输出。。。但为什么最后一次输出是A2?执行/3
时,A不应该将x
视为4吗
奖金问题:为什么x
不可能变成5
编辑
这个问题(以稍微不同的形式)来自OCP认证的模拟测试,其中解释说明
x
永远不会是5。我很高兴看到我不是唯一一个不同意的人。当您在一个线程中更新变量的值时,它的值不一定立即对所有线程可见。这是因为内存保存在CPU缓存中,这使得它的读写速度比主内存快得多
缓存的更新内容会定期复制到主内存。只有在这种情况下,其他线程才能看到值的更新
这里看起来发生的是B正在更新值,但该值没有提交到主内存;因此,A看到了它的旧价值
如果将变量设置为volatile
,则所有读写操作都直接从主存执行(或者,至少缓存从主存刷新/刷新到主存),因此所有线程都可以立即看到对值的更新
但是请注意,您没有执行原子读写:在当前线程检查x<4
和递增x++
之间,另一个线程可能会更新x
的值。因此,最终可能会打印值5
解决此问题的最简单方法是使检查/增量同步:
synchronized (Main.class) {
if (x < 4) {
x++;
System.out.println(...);
}
}
synchronized(Main.class){
if(x<4){
x++;
System.out.println(…);
}
}
这还可以确保所有线程中对
x
的更新的可见性,但也可以确保只有一个线程可以一次检查/递增x
。在一个线程中更新变量的值时,其值不必立即对所有线程可见。这是因为内存保存在CPU缓存中,这使得它的读写速度比主内存快得多
缓存的更新内容会定期复制到主内存。只有在这种情况下,其他线程才能看到值的更新
这里看起来发生的是B正在更新值,但该值没有提交到主内存;因此,A看到了它的旧价值
如果将变量设置为volatile
,则所有读写操作都直接从主存执行(或者,至少缓存从主存刷新/刷新到主存),因此所有线程都可以立即看到对值的更新
但是请注意,您没有执行原子读写:在当前线程检查x<4
和递增x++
之间,另一个线程可能会更新x
的值。因此,最终可能会打印值5
解决此问题的最简单方法是使检查/增量同步:
synchronized (Main.class) {
if (x < 4) {
x++;
System.out.println(...);
}
}
synchronized(Main.class){
if(x<4){
x++;
System.out.println(…);
}
}
这还可以确保所有线程中
x
更新的可见性,但也可以确保只有一个线程可以一次检查/增加x
。这是一个典型的争用条件。当您调用th1.start()
&th2.start()
时,它只调度线程启动,而不按顺序启动。因此,您的实际线程可以并且确实以任何旧顺序启动。现在,在while(x之间,这是一个典型的竞争条件。当您调用th1.start()
&th2.start()时
它只安排线程启动,而不是按顺序启动。因此,您的实际线程可以并且确实可以按任何旧顺序启动。现在,添加一个事实,即在之间,而(x您的变量x
是静态的,因此它在两个线程之间共享
线程B
将x
增加到4
并完成,同时编写每个步骤
线程A
在1
时有一次机会查看x
,因此增加它的值并打印A2
。下次看到x
时,它位于>=4
,因此退出循环
奖金问题-是的,x
有可能变成5
,甚至打印成5
。如果两个线程都检查x您的变量x
是静态的
,因此它在两个线程之间共享
线程B
将x
增加到4
并完成,同时编写每个步骤
线程A
在1
时有一次机会查看x
,因此增加它的值并打印A2
。下次看到x
时,它位于>=4
,因此退出循环
奖金问题-是的,可能x
变成5
,甚至打印成5
。如果两个线程都检查x知道start是异步方法调用,那么第一个线程将在另一个线程之前启动。
第二:x是静态的,但在本地上下文中,意味着第一个运行的线程将在第二个线程仍处于睡眠状态时更改x(当第二个线程处于睡眠状态时,第二个线程有一个本地存储的本地静态x值,他将在醒来后使用该值)
在那之后,第二个t
x++;