System.out.println 且 java 不稳定

System.out.println with java volatile

我有一个这样的例子:

public class MainApp {

    private volatile static int MY_INT = 0;

    public static void main(String[] args) {
        new Thread1().start();
        new Thread2().start();
    }

    static class Thread1 extends Thread {
        @Override
        public void run() {
            while(true) {
                MY_INT++;
                System.out.println("1 : " + MY_INT);
            }
        }
    }

    static class Thread2 extends Thread{
        @Override
        public void run() {
            while(true) {
                MY_INT++;
                System.out.println("2 : " + MY_INT);
            }
        }
    }
}

输出为:

1 : 1
2 : 2
1 : 3
2 : 4
1 : 5
1 : 7
1 : 8
1 : 9
1 : 10
2 : 6
1 : 11
1 : 13

我不明白为什么在打印1:10之后下一行是2:6。任何人都可以解释结果吗?提前致谢

这里有几个问题:

  • 线程不能运行并行。它们在时间片中 运行(默认值:PC 上为 15.6 毫秒;每秒 64 个滴答,参见 timer resolution (Microsoft))。这就是为什么你看不到 1:x2:x 一个接一个,而是几个 1:x 接一个
  • 使用 volatile 对同步没有帮助。您需要真正的同步对象,例如 AtomicIntegersynchronized 关键字。因此,您可能会看到跳过的数字(在您的输出中不是这种情况,但它可能会发生)。如果你想看到唯一的数字
  • ,你需要同步 ++println()
  • 控制台输出经过缓冲和同步,因为您不希望 2 println 语句在一行中混合输出

System.out中的PrintStream和volatile字段MY_INT是独立同步的,所以会出现以下情况:

Thread 1               Thread 2
read MY_INT = 4
write MY_INT = 5
read MY_INT = 5
                       read MY_INT = 5
                       write MY_INT = 6
                       read MY_INT = 6
println 5
read MY_INT = 6
write MY_INT = 7
read MY_INT = 7
println 7

...
                       println 6

即因为volatile字段和System.out返回的PrintStream是独立同步的,所以打印可能是非升序的

也可能发生以下情况:

Thread 1            Thread 2
read MY_INT = 1
                    read MY_INT = 1
write MY_INT = 2
                    write MY_INT = 2
read MY_INT = 2
println 2
                    read MY_INT = 2
                    println 2

因为++MY_INT实际上是编译成一个读,一个计算,一个写。由于易失性读取和写入是单独的同步操作,其他线程可能会在两者之间操作,并弄乱计数器。

如果您希望升序数字由单独的线程打印,最简单的方法是:

void run() {
   while (true) {
       synchronized (lock) {
           MY_INT++;
           System.out.println("1 : " + MY_INT);
       }
   }
}

其中 lock 是所有访问 MY_INT 的线程共享的对象。