2016-01-20 46 views
0

我嘗試創建一個線程安全的類,它允許跟蹤某些內容的掃描。我的階級是:線程安全類中的意外行爲

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()返回一些東西。

我該怎麼做才能糾正這個問題?

+0

我認爲您需要創建一個更簡單的代碼示例,以便您瞭解真正的問題。目前還不清楚你想要做什麼。 –

+1

你的領域是原子的,但你提供的方法不是。因此它們本身不是安全的。但即使它們是,調用兩種方法也會使您再次同步。所以你在這裏有兩個問題:當你相信你是線程安全的時候,你的線程安全也是無效的。 – Fildor

+0

是的,當兩個線程更新您的計數器時會發生這種情況,其中一個線程仍在中間(添加到響應中,但尚未發生錯誤)。但爲什麼這是一個問題?如果您在給定時間需要兩個計數器的一致snapsnots,那麼您需要添加同步。理想情況下,您可以避免需要這樣做(例如,您的「isDone」應該仍然有效,因爲它只需要一個計數器)。 – Thilo

回答

0

只要我unsterstand問題,我認爲你需要使用鎖。在獲取或設置變量之前,您需要獲取一個鎖。這樣一個變量不能同時設置和讀取。你得到類似下面的代碼。

public final static Object LOCK = new Object(); 

private int yourvariable; 

public void setVar(int var){ 
    synchronized(LOCK){ 
     yourvariable = var; 
    } 
} 

public int getVar(){ 
    int toReturn; 
    synchronized(LOCK){ 
     toReturn = yourvariable; 
    } 
    return toReturn; 
} 

提示:如果您ScanInProgessClass是唯一的ScanInProgressClass類,你可以使用this而不是鎖定對象。

+0

這仍然只在個別計數器級別上同步,所以它似乎沒有改進當前的解決方案(AtomicInteger已經照顧到了這一點)。 – Thilo

+0

使用靜態對象進行鎖定不是一種好的做法。 – cheb1k4