1

volatileはJavaのキーワードについてかなり良い考えを持っていると思いますが、いくつかのコードをリファクタリングすることを考えていて、それを使うのは良い考えだと思いました.

基本的にDBキャッシュとして機能するクラスがあります。データベースから読み取った一連のオブジェクトを保持し、それらのオブジェクトの要求を処理し、(タイムアウトに基づいて) ときどきデータベースを更新します。骸骨はこちら

public class Cache
{
    private HashMap mappings =....;
    private long last_update_time;
    private void loadMappingsFromDB()
    {
        //....
    }
    private void checkLoad()
    {
        if(System.currentTimeMillis() - last_update_time > TIMEOUT)
            loadMappingsFromDB();
    }
    public Data get(ID id)
    {
        checkLoad();
        //.. look it up
    }
}

そのため、loadMappingsFromDB は待ち時間の長い操作になる可能性があり、それは受け入れられないという懸念があります。そのため、最初は、キャッシュの起動時にスレッドをスピンアップし、それをスリープさせて、バックグラウンドでキャッシュを更新できると考えていました。しかし、その後、クラス (またはマップ) を同期する必要があります。そして、すべてのキャッシュアクセスを遅くするために、時折大きな一時停止を行うだけです。

それから私はなぜ使わないのかと思いましたvolatile

マップ参照を揮発性として定義できます

private volatile HashMap mappings =....;

そして、get(またはマッピング変数を使用する他の場所)で、参照のローカルコピーを作成します:

public Data get(ID id)
{
    HashMap local = mappings;
    //.. look it up using local
}

バックグラウンドスレッドは一時テーブルにロードされ、クラス内の参照を交換します

HashMap tmp;
//load tmp from DB
mappings = tmp;//swap variables forcing write barrier

このアプローチは理にかなっていますか?それは実際にスレッドセーフですか?

4

4 に答える 4

2

この質問に対する既存の回答には、いくつかの誤った情報があります。actual の使用volatileは、スレッドの安全性を確保するための良いステップです。IBM の Peter Haggar によるDispelling Java programming language mythsの項目 3 を参照してください。Haggar は少し背景と例を挙げていますが、要点は次のとおりです。

では、アトミック操作がスレッドセーフにならないのはなぜでしょうか? 要点は、それらが実際にスレッドセーフである可能性があるということですが、そうであるという保証はありません。Java スレッドは、変数のプライベート コピーをメイン メモリとは別に保持できます。

を使用volatileすることで、スレッドがメイン メモリを参照し、知らない、または予期しない変数のプライベート コピーを使用しないことが保証されます。

あなたの質問に答えると: はい、あなたの戦略は安全です。

編集:
別の投稿への応答として、フィールドに関する JLS セクションvolatileを次に示します。

于 2010-06-10T17:06:16.463 に答える
1

このアプローチは理にかなっていますか?それは実際にスレッドセーフですか?

それは理にかなっており、スレッドセーフです。とにかく、ある程度。考慮すべき点:

  • 更新時に、アプリケーションに古い古い値を読み取らせます。これはあなたが意図したものですか?一部のアプリケーションでは問題ありませんが、キャッシュが更新されるまでブロックしたい場合もあります (FutureTaskこれにより、この動作がかなり簡単になります)。
  • 起動すると、最初にloadMappingsFromDB()呼び出したスレッドget(ID)は、更新が完了するまでブロックされます。
  • 複数のスレッドがcheckLoad()同時に呼び出す可能性があります。つまり、リロードが遅く、複数のスレッドが を呼び出している場合get(ID)、同時更新のクラップロードが発生する可能性があります。結果は同じですが、システム リソースが無駄になります。現在のコードでそれを修正する簡単な方法は、AtomicBoolean更新する前に確認することです。

    private final AtomicBoolean isUpdating = new AtomicBoolean(false);
    private void checkLoad()
    {
        if (System.currentTimeMillis() - last_update_time <= TIMEOUT) return;
        if (!isUpdating.compareAndSet(false, true)) return; // already updating
        try {
            loadMappingsFromDB();
        } finally {
            isUpdating.set(false);
        }
    }
    
于 2010-06-10T16:58:39.693 に答える
0

この一般的なアプローチは有効だと思います(バックグラウンドスレッドでキャッシュを再ロードし、データを個別のインスタンスにロードすることでロードの進行中にキャッシュへのアクセスを妨げません)が、参照をvolatile実際に宣言するものがわからないあなたにあげる。

get(id)メソッドとloadMappingsFromDB()参照を上書きする部分 (DB からのロード全体ではなく、 の再割り当てのみ) を同じロックで簡単にmappings同期させることができます。

正直なところ、EhCache のようにバックグラウンドでの読み込みや起動時の読み込みの機能を備えた確立されたキャッシュ ライブラリを再利用することを検討したいと思いますが、これらのライブラリはかなり前に同期の問題をすべて解決している可能性が高く、元に戻すことができます。自家製のキャッシュの低レベルの安全性よりも、アプリのロジックについて心配する必要があります。

于 2010-06-10T16:29:00.700 に答える
-1

実際、提案されたアプローチは、「volatile」キーワードを使用しなくても意味があります。参照の代入 ( mappings = tmp;) はアトミック操作 (1 つのマシン コマンドに変換される) であるため、メモリの不一致の可能性はありません:
http://java.sun.com/docs/books/tutorial/essential/concurrency/atomic.html
参照変数とほとんどのプリミティブ変数 (long と double を除くすべての型) のアトミック。

「揮発性」キーワードの背後にある考え方は、時間 T でそのような変数を変更すると、時間 T + x (x > 0) でそれにアクセスする他のスレッドは新しい値を見るということです。そうしないと、値が変更された後でも、他のスレッドが古い値を参照できる期間が残ります。しかし、それはあなたにとって問題ではないようです。
編集
上記のリンクで同じアイデアが説明されています。

于 2010-06-10T16:53:46.070 に答える