2012-11-22 145 views
2

我正在創建一個項目,以創建多個客戶端連接到的簡單拍賣服務器。服務器類實現Runnable,併爲每個連接的客戶端創建一個新的線程。 我想要將當前最高出價存儲在每個客戶可以看到的變量中。我找到答案說使用AtomicInteger,但是當我用它與atomicVariable.intValue()方法時,我得到了空指針異常錯誤。Java服務器客戶端,線程間共享變量

有什麼方法可以操作AtomicInteger而不會出現此錯誤,或者是否有其他方式可以使共享變量相對簡單?

任何幫助,將不勝感激,謝謝。

更新

我有工作的AtomicInteger。現在的問題是,只有最近的客戶端連接到服務器似乎能夠與它進行交互。另一個客戶只是有點凍結。

我是否正確地說這是鎖定問題?

+2

如果沒有看到你的代碼,我們真的不知道什麼是錯的。你是否曾經初始化你的'atomicVariable'變量? –

回答

4

那麼,最有可能你忘了初始化:

private final AtomicInteger highestBid = new AtomicInteger(); 

然而,隨着highestBid工作需要知識的大量工作得到它的權利,沒有任何鎖定。例如,如果你想與新的最高報價來更新它:

public boolean saveIfHighest(int bid) { 
    int currentBid = highestBid.get(); 
    while (currentBid < bid) { 
     if (highestBid.compareAndSet(currentBid, bid)) { 
      return true; 
     } 
     currentBid = highestBid.get(); 
    } 
    return false; 
} 

或在更緊湊的方式:

for(int currentBid = highestBid.get(); currentBid < bid; currentBid = highestBid.get()) { 
    if (highestBid.compareAndSet(currentBid, bid)) { 
     return true; 
    } 
} 
return false; 

你可能會問,爲什麼這麼難?圖像兩個線程(請求)同時出現。目前最高報價是10.一個是11,另一個12。兩個線程比較當前highestBid並意識到他們更大。現在第二個線程恰好是第一個,並將其更新爲12.不幸的是,第一個請求現在進入並將其恢復爲11(因爲它已經檢查了條件)。

這是一種典型的競態條件,您可以通過顯式同步或通過使用具有隱式比較和設置底層支持的原子變量來避免這種情況。

眼見通過更高性能的無鎖的原子整數帶來的複雜性你可能要恢復到經典同步:

public synchronized boolean saveIfHighest(int bid) { 
    if (highestBid < bid) { 
     highestBid = bid; 
     return true; 
    } else { 
     return false; 
    } 
} 
+0

它可以是靜態的,因爲線程之間共享標題建議?如果是這樣,需要鎖定,對吧? – DarthVader

+0

@DarthVader:它可以是靜態的,它不需要同步(這就是爲什麼它被稱爲「原子」),但它是非常棘手的,沒有鎖定正確。 –

+0

是否需要靜態取決於您的Servlet運行器(安裝Tomcat等)。最常見的情況是,每個servlet類只有一個實例,所以對於變量是否爲靜態,它沒有任何區別。但這可能會有所不同。 –

1

使用的是的AtomicInteger罰款,只要你初始化它作爲托馬斯曾建議。

然而,您可能想要考慮的是您是否真的需要存儲的只是整數的最高出價。您是否永遠不需要存儲相關信息,例如投標時間,投標人的用戶ID等?因爲如果你在後面的階段,你必須開始撤消你的AtomicInteger代碼並取代它。

我會從一開始就試探設置的東西來存儲與出價相關的任意信息。例如,您可以使用相關字段定義「Bid」類。然後在每個出價上,使用一個AtomicReference來存儲「Bid」的實例以及相關信息。要成爲線程安全的,請確保您的Bid類中的所有字段都是最終的。

您也可以考慮使用顯式鎖定(例如,參見ReentrantLock類)來控制對最高出價的訪問。正如Tomasz所提到的,即使使用AtomicInteger(或AtomicReference:邏輯本質上是相同的),您也需要對如何訪問它有點小心。原子類實際上是針對經常訪問的情況而設計的(如每秒數千次,而不是像典型拍賣網站上的每隔幾分鐘)。它們在這裏不會給你帶來任何性能優勢,而且一個明確的Lock對象可能更直觀地進行編程。

2

我不會看這樣的問題。我只需將所有出價存儲在ConcurrentSkipListSet中,這是一個線程安全的SortedSet。在確定訂單的正確實施compareTo()後,Set的第一個元素將自動成爲最高出價。

下面是一些示例代碼:

public class Bid implements Comparable<Bid> { 
    String user; 
    int amountInCents; 
    Date created; 

    @Override 
    public int compareTo(Bid o) { 
     if (amountInCents == o.amountInCents) { 
      return created.compareTo(created); // earlier bids sort first 
     } 
     return o.amountInCents - amountInCents; // larger bids sort first 
    } 
} 

public class Auction { 
    private SortedSet<Bid> bids = new ConcurrentSkipListSet<Bid>(); 

    public Bid getHighestBid() { 
     return bids.isEmpty() ? null : bids.first(); 
    } 

    public void addBid(Bid bid) { 
     bids.add(bid); 
    } 
} 

這樣做有以下優點:

  • 自動提供投標歷史
  • 允許一個簡單的方法來拯救你需要
  • 任何其他出價信息

你也可以考慮這種方法:

/** 
* @param bid 
* @return true if the bid was successful 
*/ 
public boolean makeBid(Bid bid) { 
    if (bids.isEmpty()) { 
     bids.add(bid); 
     return true; 
    } 
    if (bid.compareTo(bids.first()) <= 0) { 
     return false; 
    } 
    bids.add(bid); 
    return true; 
}