由于 system.out.println 语句导致 运行 线程延迟
Delay in running thread due to system.out.println statement
在下面的代码中,如果我在 for 循环中使用 sysout 语句,那么代码会在满足条件后执行并进入循环,但是如果我不在循环中使用 sysout 语句,那么无限循环将继续下去而不进入内部if 条件即使满足 if 条件。任何人都可以帮我找出确切的原因。只是一条 sysout 语句使 if 条件变为真。为什么会这样?
代码如下:-
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name){
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
for(;;)
{
//Output 1: without this sysout statement.
//Output 2: After uncommenting this sysout statement
//System.out.println(Thread.currentThread().isInterrupted());
if(TestThread.i>3)
{
try {
for(int j = 4; j > 0; j--) {
System.out.println("Thread: " + threadName + ", " + j);
}
} catch (Exception e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
}
}
public void start ()
{
System.out.println("Starting " + threadName );
if (t == null)
{
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
static int i=0;
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i+=4;
System.out.println(i);
}
}
无限循环中没有sysout语句的输出:-
在无限循环中用sysout语句输出:-
当您访问变量值时,更改不会每次都写入(或加载)实际内存位置。该值可以加载到 CPU 寄存器中,或缓存,并保留在那里直到缓存被刷新。此外,因为 TestThread.i
根本没有在循环内部被修改,优化器可能决定在循环之前用检查替换它,并完全摆脱 if
语句(我不认为它你的情况实际上正在发生,但关键是它 可能 ).
使线程刷新其缓存并将其与物理内存的当前内容同步的指令称为内存屏障。在 Java 中有两种方法可以强制内存屏障:进入或退出 synchronized
块或访问 volatile
变量。
当这些事件中的任何一个发生时,缓存被刷新,并且线程保证看到内存内容的 up-to-date 视图,并将它所做的所有更改本地提交到内存。
所以,正如评论中所建议的,如果你将 TestThread.i
声明为 volatile
,问题就会消失,因为每当修改值时,都会立即提交更改,并且优化器将知道不要优化器,将检查从循环中移开,而不是缓存值。
现在,为什么添加打印语句会改变行为?好吧,在 io 内部有很多同步正在进行,线程在某处遇到内存屏障,并加载新值。这只是巧合。
这里的问题可以通过更改
来解决
static int i=0;
到
static volatile int i=0;
创建变量 volatile
会产生许多复杂的后果,我不是这方面的专家。因此,我将尝试解释我的想法。
变量 i
存在于您的主内存中,即 RAM。但是 RAM 很慢,所以你的处理器将它复制到更快(和更小)的内存:缓存。实际上有多个缓存,但这无关紧要。
但是当两个不同处理器上的两个线程将它们的值放在不同的缓存中时,当值发生变化时会发生什么?好吧,如果线程 1 更改了缓存 1 中的值,线程 2 仍然使用缓存 2 中的旧值。除非我们告诉两个线程这个变量 i
可能会随时发生变化,就好像它是魔术一样。这就是 volatile 关键字的作用。
那么为什么它与 print 语句一起工作?好吧,print 语句在幕后调用了很多代码。这段代码中的一些很可能包含一个同步块或另一个易失性变量,它们(意外地)也刷新了两个缓存中 i
的值。 (感谢 Marco13 指出这一点)。
下次您尝试访问 i
时,您将获得更新后的值!
PS:我在这里说的是 RAM,但它可能是两个线程之间最接近的共享内存,例如,如果它们是超线程的,它可能是一个缓存。
这个解释也很好(有图片!):
在下面的代码中,如果我在 for 循环中使用 sysout 语句,那么代码会在满足条件后执行并进入循环,但是如果我不在循环中使用 sysout 语句,那么无限循环将继续下去而不进入内部if 条件即使满足 if 条件。任何人都可以帮我找出确切的原因。只是一条 sysout 语句使 if 条件变为真。为什么会这样?
代码如下:-
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name){
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
for(;;)
{
//Output 1: without this sysout statement.
//Output 2: After uncommenting this sysout statement
//System.out.println(Thread.currentThread().isInterrupted());
if(TestThread.i>3)
{
try {
for(int j = 4; j > 0; j--) {
System.out.println("Thread: " + threadName + ", " + j);
}
} catch (Exception e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
}
}
public void start ()
{
System.out.println("Starting " + threadName );
if (t == null)
{
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
static int i=0;
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i+=4;
System.out.println(i);
}
}
无限循环中没有sysout语句的输出:-
在无限循环中用sysout语句输出:-
当您访问变量值时,更改不会每次都写入(或加载)实际内存位置。该值可以加载到 CPU 寄存器中,或缓存,并保留在那里直到缓存被刷新。此外,因为 TestThread.i
根本没有在循环内部被修改,优化器可能决定在循环之前用检查替换它,并完全摆脱 if
语句(我不认为它你的情况实际上正在发生,但关键是它 可能 ).
使线程刷新其缓存并将其与物理内存的当前内容同步的指令称为内存屏障。在 Java 中有两种方法可以强制内存屏障:进入或退出 synchronized
块或访问 volatile
变量。
当这些事件中的任何一个发生时,缓存被刷新,并且线程保证看到内存内容的 up-to-date 视图,并将它所做的所有更改本地提交到内存。
所以,正如评论中所建议的,如果你将 TestThread.i
声明为 volatile
,问题就会消失,因为每当修改值时,都会立即提交更改,并且优化器将知道不要优化器,将检查从循环中移开,而不是缓存值。
现在,为什么添加打印语句会改变行为?好吧,在 io 内部有很多同步正在进行,线程在某处遇到内存屏障,并加载新值。这只是巧合。
这里的问题可以通过更改
来解决static int i=0;
到
static volatile int i=0;
创建变量 volatile
会产生许多复杂的后果,我不是这方面的专家。因此,我将尝试解释我的想法。
变量 i
存在于您的主内存中,即 RAM。但是 RAM 很慢,所以你的处理器将它复制到更快(和更小)的内存:缓存。实际上有多个缓存,但这无关紧要。
但是当两个不同处理器上的两个线程将它们的值放在不同的缓存中时,当值发生变化时会发生什么?好吧,如果线程 1 更改了缓存 1 中的值,线程 2 仍然使用缓存 2 中的旧值。除非我们告诉两个线程这个变量 i
可能会随时发生变化,就好像它是魔术一样。这就是 volatile 关键字的作用。
那么为什么它与 print 语句一起工作?好吧,print 语句在幕后调用了很多代码。这段代码中的一些很可能包含一个同步块或另一个易失性变量,它们(意外地)也刷新了两个缓存中 i
的值。 (感谢 Marco13 指出这一点)。
下次您尝试访问 i
时,您将获得更新后的值!
PS:我在这里说的是 RAM,但它可能是两个线程之间最接近的共享内存,例如,如果它们是超线程的,它可能是一个缓存。
这个解释也很好(有图片!):