2

複数のクライアントが接続する単純なオークション サーバーを作成するプロジェクトに取り組んでいます。サーバー クラスは Runnable を実装するため、接続するクライアントごとに新しいスレッドを作成します。各クライアントが見ることができる変数に現在の最高入札額を保存しようとしています。AtomicInteger を使用するようにという回答が見つかりましたが、atomicVariable.intValue() などのメソッドで使用すると、null ポインター例外エラーが発生しました。

このエラーを取得せずに 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;

なぜそんなに難しいの?と思うかもしれません。2 つのスレッド (リクエスト) が同時に入札しているイメージ。現在の最高入札額は 10 です。一方は 11 を入札し、もう 1 つは 12 を入札しています。両方のスレッドが現在highestBidを比較し、それらの方が大きいことに気付きます。ここで、たまたま 2 番目のスレッドが最初になり、それを 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

Tomaszが提案したように初期化すれば、AtomicIntegerの使用は問題ありません。

ただし、考えたいのは、文字通り保存する必要があるのは整数としての最高入札額だけかどうかということです。入札時間、入札者のユーザー ID などの関連情報を保存する必要はありませんか? 後の段階で行う場合は、AtomicInteger コードを元に戻し、それを置き換える必要があるためです。

最初から、入札に関連する任意の情報を保存するように設定したくなるでしょう。たとえば、関連するフィールドで「入札」クラスを定義できます。次に、入札ごとに、AtomicReference を使用して、「入札」のインスタンスを関連情報とともに保存します。スレッドセーフにするには、Bid クラスのすべてのフィールドを final にします。

明示的な Lock (ReentrantLock クラスなどを参照) を使用して最高入札額へのアクセスを制御することも検討できます。Tomasz が言及しているように、AtomicInteger (または AtomicReference: ロジックは本質的に同じ) を使用しても、アクセス方法について少し注意する必要があります。アトミック クラスは、非常に頻繁にアクセスされる場合 (典型的なオークション サイトのように数分ごとではなく、1 秒あたり数千回など) に合わせて設計されています。ここでは、実際にはパフォーマンス上の利点はありません。また、明示的な Lock オブジェクトを使用すると、プログラミングがより直感的になる場合があります。

于 2012-11-22T17:55:07.327 に答える