Java Server Client,线程之间的共享变量

时间:2012-11-22 17:48:08

标签: java multithreading client

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

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

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

更新

我让AtomicInteger正常工作。问题是现在只有连接到服务器的最新客户端似乎能够与它进行交互。另一个客户只是冻结了。

我说这是锁定问题吗?

3 个答案:

答案 0 :(得分: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;
    }
}

答案 1 :(得分: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;
}

答案 2 :(得分:1)

使用AtomicInteger很好,只要你像Tomasz建议的那样初始化它。

然而,您可能想要考虑的是,您是否真正需要存储的只是整数的最高出价。您是否永远不需要存储相关信息,例如投标时间,投标人的用户ID等?因为如果你在以后的阶段,你必须开始撤消你的AtomicInteger代码并替换它。

我会从一开始就试图设置存储与出价相关的任意信息。例如,您可以使用相关字段定义“出价”类。然后,在每次出价时,使用 AtomicReference存储“Bid”的实例以及相关信息。要确保线程安全,请将Bid类中的所有字段设为最终字段。

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