簡體   English   中英

Java多線程:意外的結果

[英]Java Multi-threading : Unexpected result

我正在開發一個企業應用程序。 我在多線程環境中運行應用程序時遇到了一些問題。 我正在編寫一個程序,其中有一個變量,其值以非常快的速率更新(遞增)(例如10000次更新/ persecond)。 循環運行一定的迭代,變量的值遞增並存儲在HashMap中。 一旦循環終止並且值打印HashMap中的變量。 我得到了變量的意外價值。

這是演示程序(請閱讀評論以便更好地理解):

class test implements Runnable {

    static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    static AtomicInteger value_to_be_incremented_stored = new AtomicInteger(0); // variable whose value to be updated
    static AtomicInteger i = new AtomicInteger(0);  // this runs the loop

    @Override
    public void run() {

        for (i.set(0); i.get() < 100000; i.incrementAndGet()) {
            /*
                This loop should run 100000 times and when loop terminates according to me value of variable 
                "value_to_be_incremented_stored" should be 100000 as its value is incremented 
                100000 times the loop also runs 100000 times. 
            */
            System.out.println("Thread > " + Thread.currentThread() + "  " + value_to_be_incremented_stored.incrementAndGet());
            map.put("TC", value_to_be_incremented_stored.intValue());
        }

        System.out.println("Output by Thread  " + Thread.currentThread() + "     " + map.toString());
    }

    public static void main(String[] args) {

        test t1 = new test();
        Thread thread1 = new Thread(t1);
        thread1.setName("Thread 1");

        Thread thread2 = new Thread(t1);
        thread2.setName("Thread 2");

        Thread thread3 = new Thread(t1);
        thread3.setName("Thread 3");

        Thread thread4 = new Thread(t1);
        thread4.setName("Thread 4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

輸出 (變化):

我的輸出

問題 :我運行循環100000次(i.get() < 100000)然后變量value_to_be_incremented_stored值變得超過100000。

我發現了三個缺陷。 在比較循環計數器的點與增加循環計數器的位置之間的for循環中存在競爭條件。 您應該在一個步驟中執行此操作以獲取原子操作:

for ( ; i.incrementAndGet() < 100000;  ) {

另一個是你的計數器增量和放在地圖之間也存在競爭條件。 即使你將它們串聯遞增,任何線程都可以在內部具有不同的值(它位於循環中的不同點),並且它可以將先前的值放在全局映射中。 這里需要原子性以確保增加的值是您在循環中放置的值。

synchronized( lock ) { 
    value_to_be_incremented_stored.incrementAndGet();
    map.put("TC", value_to_be_incremented_stored.intValue());
}

最后由於某種原因, <比較為我產生了99999的值。 我不得不用<=來修復它。

(正如我們在評論中所討論的那樣,在每個for循環開始時設置i.set(0)並不是很明顯的原因。我猜想有四個缺陷。)

class ThreadTestX implements Runnable {

    static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    static AtomicInteger value_to_be_incremented_stored = new AtomicInteger(0); // variable whose value to be updated
    static AtomicInteger i = new AtomicInteger(0);  // this runs the loop
    static final Object lock = new Object();

    @Override
    public void run() {

        for ( ; i.incrementAndGet() <= 100000;  ) {
            /*
                This loop should run 100000 times and when loop terminates according to me value of variable 
                "value_to_be_incremented_stored" should be 100000 as its value is incremented 
                100000 times the loop also runs 100000 times. 
            */
            synchronized( lock ) {
                value_to_be_incremented_stored.incrementAndGet();
    //            System.out.println("Thread > " + Thread.currentThread() + 
    //                 "  " + value_to_be_incremented_stored.get());
                map.put("TC", value_to_be_incremented_stored.intValue());
            }
        }

        System.out.println("Output by Thread  " + Thread.currentThread() 
                + "     " + map.toString());
    }

    public static void main(String[] args) {

        ThreadTestX t1 = new ThreadTestX();
        Thread thread1 = new Thread(t1);
        thread1.setName("Thread 1");

        Thread thread2 = new Thread(t1);
        thread2.setName("Thread 2");

        Thread thread3 = new Thread(t1);
        thread3.setName("Thread 3");

        Thread thread4 = new Thread(t1);
        thread4.setName("Thread 4");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

輸出:

run:
Output by Thread  Thread[Thread 4,5,main]     {TC=100000}
Output by Thread  Thread[Thread 3,5,main]     {TC=100000}
Output by Thread  Thread[Thread 1,5,main]     {TC=100000}
Output by Thread  Thread[Thread 2,5,main]     {TC=100000}
BUILD SUCCESSFUL (total time: 0 seconds)

事后判斷:盡管標記正確,但我不確定我是否正確。 這里的問題是你試圖讓三件事情保持同步:循環計數器i ,要增加的值和地圖。 允許在同步塊之外執行這些中的任何一個可以邀請它們處於意外狀態。 我認為以下可能更安全:

@Override
public void run() {

    for ( ;;  ) {
        synchronized( lock ) {
            if( i.incrementAndGet() <= 100000 ) {
                value_to_be_incremented_stored.incrementAndGet();
                map.put("TC", value_to_be_incremented_stored.intValue());
            }
            else
                break;
        }
    }
    System.out.println("Output by Thread  " + Thread.currentThread() 
            + "     " + map.toString());
}

這消除了將變量聲明為AtomicInteger的需要,但是我沒有看到如何確保它們的值在該循環執行時不會改變(由於某些其他線程)。

您的“演示程序”會同時出現兩個缺陷。

缺陷#1是i.set( 0 )指出的i.set( 0 ) 你說你修正了,但你仍然得到超過100000的值。我也嘗試過它,實際上,最終值仍然大於100000。

我修改了程序以便能夠創建任意數量的線程,我嘗試使用3,10和20個線程。 我的最終數字分別為100003,100009和100019。 看模式?

會發生的是,在最后一次迭代中,當i值為99999時,表達式i.get() < 100000對於所有線程都為真,因此所有線程都會再次執行。 i.incrementAndGet()子句直觀地坐在i.get() < 1000000;旁邊i.get() < 1000000; 但它直到循環結束才會執行。

因此,所有線程都有機會在最后一次迭代后再次增加i

每當新線程進入run方法時,它將通過調用i.set(0)通過for循環中的第一個語句將i count重置為零。

更新:修復重置問題后的下一步是逐步執行代碼並查看線程的行為方式。 讓我們說三個線程進入for循環,而第四個線程增加i 會發生什么是value_to_be_incremented_stored將增加3次而i只增加一次。

暫無
暫無

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

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