4 回答

TA貢獻1829條經驗 獲得超4個贊
您的代碼存在兩個并發問題。
對于并發任務,共享 int
i
既沒有適當聲明也沒有訪問。它至少需要是易失性的,但為了正確處理原子增量應該是一個AtomicInteger
. Volatile 將有助于保證其他線程可以看到值是否發生變化(JVM 無法保證這一點),但原子整數更進一步,它提供了原子操作,這才是您真正需要的。您的循環可以同時被兩個線程訪問,因為條件檢查與變量的增量是分開的。要正確處理運行循環的并發線程,您需要
while
此處的循環條件也將增量作為單個原子操作執行。或者你需要添加一個同步塊。
也許嘗試類似下面未經測試的代碼:
public class Counter {
private final AtomicInteger i = new AtomicInteger(1);
public void add() {
for (;;)
{
int localCount = i.getAndIncrement();
if (localCount > 5) { break; }
System.out.println(localCount + " " + Thread.currentThread().getName());
}
}
}
逐行解釋一下上面的代碼:
我們切換
i
到 anAtomicInteger
,因為它允許我們獲取當前值并自動將下一個值加一。這種原子性很重要,因為否則兩個競爭線程可能會增加到相同的值,而不是在所有線程中讀取和增加每個值恰好一次。我們可以通過將代碼的遞增部分放入同步塊中來實現相同的目的。我們切換到一個
for (;;)
循環,以便我們可以將循環控制/終止代碼移動到循環體內。這使我們能夠輕松地將原子增量的結果用于控制代碼和循環體內(例如在語句中println
)。i.getAndIncrement()
原子地增加我們的計數器并返回給我們增加之前的值。無論有多少線程在運行,都可以保證此操作有效,并確保我們不會遺漏或重復任何數字。將先前的計數值分配給localCount
int 為我們提供了在循環中使用的值。如果我們嘗試i
在循環內重新訪問而不是使用 localCount,那么我們最終會在某個時刻得到不同的數字,因為其他線程正在同時遞增i
。我們檢查
localCount
它是否超出了我們的終止限制,如果超過了,我們就中斷循環。我們不檢查,i
因為它可能已被另一個線程修改,并且我們只關心該線程當前的數量是否超出限制。最后,我們使用
localCount
instead of 來執行 printlni
,因為i
在代碼到達此處時可能已被另一個線程修改(這將導致打印重復行)。
請注意,此代碼不保證計數器的數字將按順序打印。 很可能大多數時候都是這種情況,但肯定不能保證也不應該依賴它。您的問題沒有說明實際的預期/要求的結果,如果這是一項要求,那么您很可能需要圍繞增量和 println 語句進行同步,以確保不會亂序執行。
例如同步解決方案:
public class Counter {
int i = 1;
public void add() {
for (;;) {
synchronized(this) {
if (i > 5) { break; }
System.out.println(i + " " + Thread.currentThread().getName());
i++;
}
}
}
}

TA貢獻1966條經驗 獲得超4個贊
這是個簡單的。
您創建了一個名為
c1
;的計數器您從此計數器創建了一個 Runnable;
您將同一個可運行對象傳遞給了兩個線程。兩個線程現在將在同一個 Counter 對象上運行
c1
;您在方法內運行 while 循環
add()
。
該add()
方法在一行上打印一些內容,并在下一行上遞增i
。
對于您當前的輸出,在第一個線程執行其 print 語句后,它被中斷,第二個線程獲得了 CPU 的控制權。第二個線程設法執行了它的打印。然后任一線程遞增計數器(我們不知道是哪一個),并且打印和計數器遞增兩個步驟繼續。

TA貢獻1859條經驗 獲得超6個贊
問題是您正在i
同時訪問該變量。Java 將變量緩存在 CPU 緩存中。由于每個線程都可以在不同的內核中執行,因此每個線程中的值i
可能不同。即使您正在執行i++
底層代碼也是i = i + 1
. 您正在讀取變量,然后正在寫入,因此在這種情況下多個線程也可能具有不同的值。
這個問題的解決方案是像你的情況一樣使用Atomic
變量。AtomicInteger
這些原子變量是線程安全的,并且像在事務中一樣讀寫,在更新變量時阻塞對線程的訪問。
在您的情況下,int
您可以聲明而不是聲明
private volatile AtomicInteger atomicInteger = new AtomicInteger(1);
而不是使用i++
你可以使用atomicInteger.addAndGet(1)

TA貢獻1890條經驗 獲得超9個贊
您剛剛發現的是競爭條件線程可以counter
不受任何限制地訪問對象。即使i
是 int,也不能保證,例如,當“t2”讀取該值時,“t1”完成了該值的遞增。嘗試運行該程序幾次,輸出可能會有所不同。
要解決你的問題,你需要實現一種鎖,或者使用關鍵字synchronized
。一般來說,在實現使用多線程的應用程序時,您需要識別關鍵部分并確保避免競爭條件。
編輯:我不敢相信我寫的protected
不是synchronized
,不知道這是怎么發生的。就在今天偶然發現了這個,編輯所以沒有其他人被誤導。
添加回答
舉報