我嘗試創建一個線程安全的類,它允許跟蹤某些內容的掃描。我的階級是:線程安全類中的意外行爲
import java.util.concurrent.atomic.AtomicInteger;
public class ScanInProgress {
private final Integer scanId;
private final int nbScans;
private AtomicInteger nbResponses = new AtomicInteger(0);
private AtomicInteger nbErrors = new AtomicInteger(0);
public ScanInProgress(Integer scanId, int nbSites) {
this.scanId = scanId;
this.nbScans = nbSites;
}
public Integer getScanId() {
return scanId;
}
public boolean addSuccess() {
addResponse();
return isDone();
}
public boolean addError() {
addResponse();
nbErrors.incrementAndGet();
return isDone();
}
private void addResponse() {
nbResponses.incrementAndGet();
}
private boolean isDone() {
return nbResponses.get() == nbScans;
}
public int getNbSuccesses() {
return nbResponses.get() - nbErrors.get();
}
public int getNbResponses() {
return nbResponses.get();
}
}
我有以下的單元測試類:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class ScanInProgressTest {
@Test
public void testConcurrency() throws Exception {
// given
Integer scanId = 1;
int nbScans = 500_000;
ScanInProgress scanInProgress = new ScanInProgress(scanId, nbScans);
// when
for (int i = 1; i <= nbScans/2; i++) {
new AddError(scanInProgress).start();
new AddSuccess(scanInProgress).start();
}
Thread.sleep(1000);
// then
assertEquals(nbScans, scanInProgress.getNbResponses());
assertEquals(nbScans/2, scanInProgress.getNbSuccesses());
}
private class AddError extends Thread {
private ScanInProgress scanInProgress;
public AddError(ScanInProgress scanInProgress) {
this.scanInProgress = scanInProgress;
}
@Override
public void run() {
int before = scanInProgress.getNbResponses();
scanInProgress.addError();
int after = scanInProgress.getNbResponses();
assertTrue("Add error: before=" + before + ", after=" + after, before < after);
}
}
private class AddSuccess extends Thread {
private ScanInProgress scanInProgress;
public AddSuccess(ScanInProgress scanInProgress) {
this.scanInProgress = scanInProgress;
}
@Override
public void run() {
int beforeResponses = scanInProgress.getNbResponses();
int beforeSuccesses = scanInProgress.getNbSuccesses();
scanInProgress.addSuccess();
int afterResponses = scanInProgress.getNbResponses();
int afterSuccesses = scanInProgress.getNbSuccesses();
assertTrue("Add success responses: before=" + beforeResponses + ", after=" + afterResponses, beforeResponses < afterResponses);
assertTrue("Add success successes: before=" + beforeSuccesses + ", after=" + afterSuccesses, beforeSuccesses < afterSuccesses);
}
}
}
當我運行我的測試,我可以經常看到這個錯誤日誌中:
Exception in thread "Thread-14723" java.lang.AssertionError: Add success successes: before=7362, after=7362
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.assertTrue(Assert.java:41)
斷言讓我認爲當我調用方法scanInProgress.addSuccess()
,然後scanInProgress.getNbSuccesses()
時,第一個方法nbResponses.incrementAndGet()
中的指令尚未被確認,而se cond方法nbResponses.get()
返回一些東西。
我該怎麼做才能糾正這個問題?
我認爲您需要創建一個更簡單的代碼示例,以便您瞭解真正的問題。目前還不清楚你想要做什麼。 –
你的領域是原子的,但你提供的方法不是。因此它們本身不是安全的。但即使它們是,調用兩種方法也會使您再次同步。所以你在這裏有兩個問題:當你相信你是線程安全的時候,你的線程安全也是無效的。 – Fildor
是的,當兩個線程更新您的計數器時會發生這種情況,其中一個線程仍在中間(添加到響應中,但尚未發生錯誤)。但爲什麼這是一個問題?如果您在給定時間需要兩個計數器的一致snapsnots,那麼您需要添加同步。理想情況下,您可以避免需要這樣做(例如,您的「isDone」應該仍然有效,因爲它只需要一個計數器)。 – Thilo