2012-11-22 99 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; 
}