簡體   English   中英

當在關鍵部分中顯示System.out.println時,多個線程顯示例外值

[英]Multiple threads showing excepted value when System.out.println is presented in critical section

我有以下代碼:

public class MyApp {
    public static void main(String[] args) throws InterruptedException {
        SharedResource sharedResource = new SharedResource();

        Runnable first = () -> {
            sharedResource.increment(10000);
        };

        Runnable second = () -> {
            sharedResource.increment(10000);
        };

        Thread thread = new Thread(first, "FirstThread");
        Thread thread2 = new Thread(second, "SecondThread");
        thread.start();
        thread2.start();
        thread.join();
        thread2.join();
        System.out.println("The value of counter is " + sharedResource.getCounter());
    }
}

使用這個類:

public class SharedResource {
    private int counter;

    public void increment(int times) {
        for (int x=1; x<=times;x++) {
            counter++;
            // System.out.println(counter);
        }
    }

    public void decrement() {
        counter--;
    }

    public int getCounter() {
        return counter;
    }
}

我很好奇為什么會一直這樣。

從increment方法中刪除System.out.println()的總值

System.out.println("The value of counter is " + sharedResource.getCounter());

是隨機的 - 這是例外,因為多個線程共享同一個counter

但是,當System.out.println(counter); 在增量方法上呈現,代碼似乎不再具有多線程問題。

計數器的最終結果總是20,000,因為代碼從每個線程中丟失了10,000次。 任何人都可以解釋我為什么會這樣嗎?

這是由於非常小的比賽窗口。

默認系統輸出是PrintStream,它是線程安全的:

public void println(int x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

所以基本上線程執行以下操作:

  1. 增量計數器(〜幾十ns)
  2. 等待上一個線程釋放鎖定,獲取它並打印到控制台(〜毫秒,慢1000倍)
  3. 轉到1

當你的關鍵部分比非關鍵部分長1000倍時,你的線程基本上是序列化的,並且計數器更新重疊的概率變得非常小,系統輸出沒有什么特別之處。

方法證明:

  1. 您可以編寫PrintStream的線程安全實現:

     public class NullPrintStream extends PrintStream { public NullPrintStream() { // Utility constant from apache-commons super(NullOutputStream.NULL_OUTPUT_STREAM); } @Override public void println(int x) { // No synchronization here } } 

然后通過System.setOut(new NullPrintStream())將其設置為系統輸出,結果將再次開始拍打。

  1. 要在開始時提供更大的競賽窗口,您可以在latch上同步runnables,以便它們幾乎同時啟動:

     CountDownLatch latch = new CountDownLatch(1); Runnable first = () -> { try { latch.await(); sharedResource.increment(10000); } catch (Exception e) { } }; // ... start your threads here latch.countDown(); 

然后,如果你運行這個樣本幾次,你會看到類似的東西(請注意我將它打印到System.err因為我已經覆蓋了System.out

計數器的值是20000

計數器的價值是19996

計數器的價值是19994

計數器的價值是19999

計數器的值是20000

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM