1

我有这段代码使用Java Thread:

public class ThreadExample implements Runnable{

    public Thread thread;
    static int i = 0;
    ThreadExample(){
        thread = new Thread(this);
        thread.start();
    }

    public static void main(String[] args) {
        ThreadExample example = new ThreadExample();
        for(int n =0; n<1000; n++){
            System.out.println("main thread "+i);
            i++;
        }
    }

    public void run(){
        for(int index=0; index<1000; index++){
            System.out.println("Sub thread "+i);
            i++;
        }
    }
}

运行时,结果是:

main thread 0
Sub thread 0
Sub thread 2
Sub thread 3
Sub thread 4
Sub thread 5
Sub thread 6
Sub thread 7
Sub thread 8
Sub thread 9
Sub thread 10
Sub thread 11
Sub thread 12
main thread 1
....

我知道运行的线程不遵循它的顺序。但是,我不明白的是:为什么主线程打印 1 (它打印变量 i),当变量 i 达到 12 时?(因为子线程已打印到 12)。

谢谢 :)

4

4 回答 4

6

最有可能的解释是,在准备打印文本和实际打印文本之间存在很大的延迟。主线程准备语句“主线程 1”,然后必须等到子线程释放锁System.out,并获取锁,才能实际打印它。

如果您使ivolatile 发生,它仍然会发生,那么这几乎是唯一的解释。

于 2012-12-22T18:03:33.400 回答
1

volatile发生这种情况是因为如果没有一些同步或关键字,线程将不会看到彼此的修改。

请参阅http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4

于 2012-12-22T18:36:58.153 回答
1

结果可能是由于锁定获取方式(发生在 System.out.println 调用的幕后)本质上并不公平。不公平的实现,或者至少部分不公平的实现,通常不能保证线程会按照它们等待的顺序获取锁。

结果是,当两个线程在一个紧密的循环中反复竞争锁时(如您的示例中所示),您通常会有一个线程进行一次获取,然后另一个线程进行一次获取,依此类推。不公平锁通常更容易实现,并且通常比高度竞争锁的完全公平替代方案提高性能(吞吐量),因为它们不会受到锁护卫效应的影响。显然,缺点是不能保证特定等待线程将获取锁的顺序,理论上某些等待线程肯定会被饿死(实际上这可能不会发生,或者锁可能被设计为当某些线程等待异常时间时回退到公平行为)。

给定不公平的锁,这种模式是自然的。您的第二个线程中的循环获得了锁定,并且在第一个线程读取 1 并开始等待后不久 - 要输出的字符串此时已经与前导文本连接,因此值 1 被“烘焙”。主线程必须等待另一个线程上的几个锁定/解锁对,此时它有机会运行,并打印旧值。

另一种解释是,由于缺少 volatile,解释器或 JIT 甚至不再读取 i 的共享值,而是将变量托管到寄存器中——这在 JMM 和 JVM 规范下是允许的,因为没有可能会修改 i 的干预方法。

于 2012-12-23T03:13:26.743 回答
0

据我所知,这是因为System.out

尝试写入文件或System.out.flush()在每个 printlen 之后调用。

并尝试i通过同步方法增加:

public synchronized void increment() {
  i++;
}
于 2012-12-22T18:15:10.557 回答