1

为什么下面的程序输出 11 而不是 12?线程不使用相同的实例变量吗?请解释?

public class Tester extends Thread {

private int i;

public static void main(String[] args){
 Tester t = new Tester();
 t.run();
 System.out.print(t.i);
 t.start();
 System.out.print(t.i);

}

public void run(){ i++;}

}

上面的代码编译得很好。i 在构造对象时默认为 0 值。在关系概念之前发生在线程启动之前执行的所有代码都已完成。这个概念是 - 实例变量在多个线程之间共享 - 这里有两个线程在运行 - 主线程和测试线程。所以我应该与两个线程共享?- 如果 i 是共享的并且如果在启动 Tester 线程之前保持发生之前的关系,那么递增 i 的值应该对 Tester 线程可见?

4

5 回答 5

3

给你的新线程时间来增加变量,试试

public static void main(String[] args){
  Tester t = new Tester();
  t.run();
  System.out.println(t.i);
  t.start();
  try {
    Thread.sleep(1000); // 1 sec
  } catch (Exception ex) {}
  System.out.println(t.i);
}

您的代码中唯一的问题是您打印该t.i值并且不等待之后t.start(),您在线程增加它之前打印该值。

于 2012-06-26T04:14:25.790 回答
2

如果您允许实例变量(例如将其公开或通过任何其他方式),则可以由多个线程访问实例变量。

在您的代码中会发生这种情况:Tester线程t将访问它自己的变量,您还将从主线程访问相同的变量。在您要求它打印值的那一刻,它可能会打印Testet线程当前所处的任何值t

当主线程调用该run方法时,它将在主线程中执行,有效地将字段的值增加到 1(因为 0 是默认值)。之后,当您调用该方法start时,它将启动单独的线程,Java VM 将调用该方法run,然后该字段再次递增,这次从 1 增加到 2。

因此,我希望输出为 1,然后可能为 1 或可能为 2,具体取决于在您要求从主线程打印值之前线程执行的任何时间......您得到的确切结果取决于在您的机器上,在另一台计算机上,它可能是另一个故事。这取决于 CPU、操作系统、可用内存和其他因素。

正如dash1e他的回答中所建议的那样,您可以在线程在后台执行Thread.sleep(1000);时让主线程等待。这大大增加了线程在主线程要求它打印之前更新字段值的可能性。也就是说,我想明确一点,使用等待任务完成本身并不够好......如果你愿意,你可以在一段时间内调用,你可以在其中验证是否满足某个条件,这就是所谓的旋转TestertTestertThread.sleep(1000);Thread.sleep

您可以从主线程打印字段的值这一事实表明您可以从另一个线程访问它。这是一件好事,因为您希望您的线程以某种方式进行通信。

访问它是可以的,因为它只是一个int,而一个int不能处于无效状态,所以不需要同步访问。虽然你可能会得到一个不是最新的值,并且它会在背景上改变,所以它不是很可靠。

如果您想要一个只能由单个线程访问的值,并且每个线程都独立存在,那么请查看ThreadLocal

于 2012-06-26T04:08:54.117 回答
0

我你的代码:

public class Tester extends Thread {

private int i;

public static void main(String[] args){
 Tester t = new Tester();
 t.run();
 System.out.print(t.i);
 t.start();
 System.out.print(t.i);

}

public void run(){ i++;}

}

你调用 t.run() 和 t.start()。有2个线程正在运行t.run()线程和t.start()线程。

在该i变量中,您在 2 个不同步的线程之间共享。

因此,有时i变量的值不会在线程之间更新。您可以使用 volatile 关键字进行同步

private volatile int i;

或者同步代码段增加值i

public void run(){ 
    synchronized(this){
        i++;
    }
}
于 2012-06-26T10:52:45.517 回答
0

您已经启动了线程,但还没有等待它停止。用于t.join()等待完成。而且,是的,您有线程同步问题,但这是另一个问题。

public class Tester extends Thread {

    private int i;

    public static void main(String[] args) throws InterruptedException {
        Tester t = new Tester();
        t.run();
        System.out.println(t.i);
        t.start();
        t.join();
        System.out.println(t.i);
    }

    public void run() {
        i++;
    }

}
于 2012-06-26T04:41:23.213 回答
0

我一直在寻找的主要答案是在发生之前:概念详述如下:

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

所以它在文档中说 - a)在线程开始之前 - 在它之前的所有语句都已完成 b)线程执行中的所有语句都在新线程终止时完成。

遵循上述理解 - 我应该一直对新线程开始可见。这应该是 - 在任何时候,在所有系统上 - 所以当调用 start 并启动一个新线程时,它应该总是将 i 视为 1。

但是,我们打印 i 值的时间是由主线程执行的,而不是由 Tester 线程执行的。因此,即使新的 Tester 线程可能将值视为 1 并将其递增为 2 - main 中的执行也不会反映它,因为 i++ 不是原子操作。

现在假设我们尝试将 int 设为:

 private volatile int i;

volatile 不仅保证特定变量的发生之前的关系,而且保证它之前的语句。

被执行的主线程的 println 可能在增量开始之前就被执行。所以我们可能会看到 11 被打印出来,即使是在变量 volatile 之后。使变量成为 AtomicInteger 也存在类似的情况。

调用时的 run 方法将看到增加的值:

System.out.println("i "+  i.incrementAndGet()); 

但不是主线程。run 方法/main 方法中数据的可见性不同。对于两个执行的线程,使用的实例变量是相同的。

于 2012-06-26T04:32:47.310 回答