簡體   English   中英

使用同步塊的Java中的並發性未給出預期結果

[英]Concurrency in Java using synchronized blocks not giving expected results

下面是一個簡單的java程序。 它有一個名為“cnt”的計數器,它會遞增,然后添加到名為“monitor”的List中。 “cnt”由多個線程遞增,並且值被多個線程添加到“監視器”。

在方法“go()”的末尾,cnt和monitor.size()應該具有相同的值,但它們不具有相同的值。 monitor.size()確實有正確的值。

如果通過取消注釋其中一個已注釋的同步塊來更改代碼,並注釋掉當前未注釋的塊,則代碼會生成預期結果。 此外,如果將線程計數(THREAD_COUNT)設置為1,則代碼會生成預期結果。

這只能在具有多個真實核心的計算機上重現。

public class ThreadTester {

    private List<Integer> monitor = new ArrayList<Integer>();
    private Integer cnt = 0;
    private static final int NUM_EVENTS = 2313;
    private final int THREAD_COUNT = 13;

    public ThreadTester() {
    }

    public void go() {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                for (int ii=0; ii<NUM_EVENTS; ++ii) {
                    synchronized( monitor) {
                        synchronized(cnt) {        // <-- is this synchronized necessary?
                            monitor.add(cnt);
                        }
//                        synchronized(cnt) {
//                            cnt++;        // <-- why does moving the synchronized block to here result in the correct value for cnt?
//                        }
                    }
                    synchronized(cnt) {
                        cnt++;              // <-- why does moving the synchronized block here result in cnt being wrong?
                    }
                }
//                synchronized(cnt) {
//                    cnt += NUM_EVENTS;    // <-- moving the synchronized block here results in the correct value for cnt, no surprise
//                }
            }

        };
        Thread[] threads = new Thread[THREAD_COUNT];

        for (int ii=0; ii<THREAD_COUNT; ++ii) {
            threads[ii] = new Thread(r);
        }
        for (int ii=0; ii<THREAD_COUNT; ++ii) {
            threads[ii].start();
        }
        for (int ii=0; ii<THREAD_COUNT; ++ii) {
            try { threads[ii].join(); } catch (InterruptedException e) { }
        }

        System.out.println("Both values should be: " + NUM_EVENTS*THREAD_COUNT);
        synchronized (monitor) {
            System.out.println("monitor.size() " + monitor.size());
        }
        synchronized (cnt) {
            System.out.println("cnt " + cnt);
        }
    }

    public static void main(String[] args) {
        ThreadTester t = new ThreadTester();
        t.go();

        System.out.println("DONE");
    }    
}

好的,讓我們來看看你提到的不同可能性:

1。

for (int ii=0; ii<NUM_EVENTS; ++ii) {
  synchronized( monitor) {
    synchronized(cnt) {        // <-- is this synchronized necessary?
      monitor.add(cnt);
    }
    synchronized(cnt) {
      cnt++;        // <-- why does moving the synchronized block to here result in the correct value for cnt?
    }
}

首先,監視器對象在線程之間共享,因此對它進行鎖定(這就是synchronized所做的)將確保塊內的代碼一次只能由一個線程執行。 因此,外部的2內部同步不是必需的,無論如何代碼都受到保護。

2。

for (int ii=0; ii<NUM_EVENTS; ++ii) {
  synchronized( monitor) {
    monitor.add(cnt);
  }
  synchronized(cnt) {
    cnt++;              // <-- why does moving the synchronized block here result in cnt being wrong?
  }
}

好的,這個有點棘手。 cnt是一個Integer對象,Java不允許修改Integer對象(整數是不可變的),即使代碼表明這是在這里發生的事情。 但實際上會發生的是cnt ++將創建一個值為cnt + 1並覆蓋cnt的新Integer。 這就是代碼實際執行的操作:

synchronized(cnt) {
  Integer tmp = new Integer(cnt + 1);
  cnt = tmp;
}

問題是,一個線程將創建一個新的cnt對象,而所有其他線程都在等待鎖定舊的cnt對象。 線程現在釋放舊的cnt,然后嘗試獲取新cnt對象的鎖定並獲取它,而另一個線程獲取舊cnt對象的鎖定。 突然,2個線程處於臨界區,執行相同的代碼並導致競爭條件。 這是錯誤結果的來源。

如果刪除第一個同步塊(帶監視器的塊),那么結果會更加錯誤,因為競爭的可能性會增加。

通常,您應該嘗試僅對最終變量使用synchronized來防止這種情況發生。

暫無
暫無

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

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