2

我正在做一个项目来创建一个多个客户端连接到的简单拍卖服务器。服务器类实现了 Runnable,因此为每个连接的客户端创建了一个新线程。我试图将当前的最高出价存储在每个客户都可以看到的变量中。我找到了使用 AtomicInteger 的答案,但是当我将它与 atomicVariable.intValue() 等方法一起使用时,我得到了空指针异常错误。

我可以通过哪些方式操作 AtomicInteger 而不会出现此错误,或者是否有其他方法可以拥有相对简单的共享变量?

任何帮助将不胜感激,谢谢。

更新

我有 AtomicInteger 工作。现在的问题是,似乎只有最近连接到服务器的客户端才能与之交互。另一个客户只是有点冻结。

我说这是锁定问题是否正确?

4

3 回答 3

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;
    }
}
于 2012-11-22T17:50:21.490 回答
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;
}
于 2012-11-22T18:07:37.977 回答
1

使用 AtomicInteger 很好,只要您按照 Tomasz 的建议对其进行初始化。

但是,您可能想考虑的是,您是否真正需要存储的只是整数形式的最高出价。您是否永远不需要存储相关信息,例如投标时间、投标人的用户 ID 等?因为如果您在稍后阶段这样做,您将不得不开始撤消您的 AtomicInteger 代码并替换它。

我从一开始就很想设置存储与投标相关的任意信息。例如,您可以使用相关字段定义“投标”类。然后在每次出价时,使用AtomicReference 来存储带有相关信息的“出价”实例。为了线程安全,请将 Bid 类中的所有字段设置为 final。

您还可以考虑使用显式锁定(例如,参见 ReentrantLock 类)来控制对最高出价的访问。正如 Tomasz 所提到的,即使使用 AtomicInteger(或 AtomicReference:逻辑本质上是相同的),您也需要小心访问它的方式。原子类实际上是为经常访问它们的情况而设计的(例如每秒数千次,而不是像典型的拍卖网站那样每隔几分钟)。它们在这里不会真正给您带来任何性能优势,并且显式 Lock 对象可能更直观地编程。

于 2012-11-22T17:55:07.327 回答