5

クラス Aクラス Bのプロジェクトには、以下のクラス構造があります。

class A {
    private String id;
    private List<B> bs = new ArrayList<B>();

    A(String id){
        this.id = id;
    }

    public List<B> getBs(){
        return bs;
    }

    public void setBs(List<B> bs){
        this.bs=bs;
    }

    public void addBs(List<B> bs){
        this.bs.addAll(bs);
    }

    public void addB(B b){
        this.bs.add(b);
    }
}

class B {
    private String id;
    private List<String> tags;

    B(String id){
        this.id = id;
    }

    public List<String> getTags(){
        return tags;
    }

    public void setTags(List<String> tags){
        this.tags = tags;
    }

    public void addTags(List<String> tags){
        this.tags.addAll(tags);
    }

    public void addTag(String tag){
        this.tags.add(tag);
    }
}

また、キャッシュ クラスもあります。

class CacheService {
    private static final ConcurrentHashMap<String, Object> CACHE = new ConcurrentHashMap<String, Object>();

    public static Object get(String id){
        return CACHE.get(id);
    }

    public static void put(String id, Object obj){
        return CACHE.put(id, obj);
    }
}

クラス A と B のオブジェクトは一意の ID を使用して作成され、<id, object> の組み合わせでこのキャッシュに入れられます。例えば:

A a1 = new A("10");
CacheService.put("10", a1);

A a2 = new A("11");
CacheService.put("11", a2);

B b1 = new B("1");
CacheService.put("1", b1);

B b2 = new B("2");
CacheService.put("2", b2);

B b3 = new B("3");
CacheService.put("3", b3);

B b4 = new B("4");
CacheService.put("4", b4);

また、クラス B オブジェクトをList<B>内側のオブジェクトa1とに入れていますa2一意の B オブジェクトは、任意の A オブジェクトに一度だけ配置されることに注意することが重要です

a1.add(b1);
a1.add(b2);

a2.add(b3);
a2.add(b4);

このようにして、CACHE にクラス A と B の複数のオブジェクトを含めることができます。

シナリオ:複数のスレッドがこの CACHE にアクセスしますが、ユーザーが指定した ID に応じて、一部のスレッドはクラス A オブジェクトを取得し、他のスレッドはクラス B オブジェクトを取得します。これらのスレッドは、実際にはこれらのオブジェクトに関する情報を読み取ったり更新したりしたいと考えています。

質問:私の要件は、スレッドがクラス A のオブジェクト (たとえばa1) にアクセスしてそれを更新した場合、他のスレッドがすべてのクラス B オブジェクト (およびこの場合)と同様に読み取りまたは更新 できないようにすることです。のすべての更新が完了するまで、内側のオブジェクト。このシナリオでロックを取得する方法を教えてください。a1b1b2List<B>a1a1

4

3 に答える 3

3

ディープ コピーを行うにはLock、A と B の各インスタンスに を持ち、クラスが独自のロックを取得し、 のすべてのロックを取得するメソッドlock()とのインターフェイスを両方に実装することをお勧めします。次に、オブジェクトを使用する前にロックし、使用後にロックを解除します。unlock()AB's

編集:したがって、あなたの行動方針は次のようになります:

  1. Lockableインターフェイスを作成し、次の2 つのメソッドでlock()呼び出しましょう。unlock()
  2. A と B の両方にそのインターフェイスを実装させます。したがって、キャッシュはLockable代わりに で動作するようになりましたObject
  3. A と B の両方にプライベート フィールドを追加する

    プライベート最終ロック lock = new ReentrantLock ();

  4. 現在、B での実装はLockable、ロックで同じメソッドを呼び出すだけです。
  5. A では、lock()lock のローカル インスタンスを取得し、b のリストを反復処理して、それらのlock()メソッドも呼び出します。同じunlock()
  6. これで、キャッシュからオブジェクトを取得するたびに、それを使って何かを行う前に、lock()そのオブジェクトを呼び出して、unlock()完了したときに呼び出します。
于 2012-10-22T14:03:00.347 に答える
2

キーワードはこれsynchronizedで役立ちます。メソッド全体を として宣言することも、定義した特定のキー オブジェクトをロックするブロックをsynchronized持つこともできます。synchronized()ブロックに入ると、synchronized()同じキーオブジェクトでロックされている他のブロックは、ブロックが出るまで別のスレッドからアクセスできません。

同期に関する Java チュートリアルを参照してください

あなたの例では、次のいずれかを実行できます。

public synchronized void addB(B b) {
    this.bs.add(b);
}

また

... クラスでロック オブジェクトを宣言します ...

private final Object LOCK = new Object();

...そしてそれをsynchronized()ブロックとして使用します:

public void addB(B b) {
    synchronized(LOCK) {
        this.bs.add(b);
    }
}

最初のものよりも 2 番目のものを使用する利点は、コードのどのセクションをロックするか (メソッド呼び出し全体だけでなく) を完全かつ明示的に制御できることです。並行性を処理する場合、効率のために同期をできるだけ少なくしたいので、これを使用すると、最小限の同期のみを実行できます。

thisまた、キーワードを使用して現在のオブジェクトを同期できるため、別のロック オブジェクトを明示的に宣言する必要はないと指摘する人もいるでしょう。ただし、この他の StackOverflow の質問と回答は、うまく行うことをお勧めしない理由をまとめたものです。

于 2012-10-22T13:49:33.807 に答える
1

EDIT2:質問が編集された後に回答を完了するつもりでしたが、クラスjava.util.concurrent.locks.ReentrantReadWriteLockを見つけました。これは、ユーザー sohanlal のニーズにぴったり合っていると思います。

このクラスは、ロックのペアを提供します。1 つは、複数の要素が同時に所有でき、それ自体はブロックしない読み取り操作用です。2 つ目 (書き込み部分) は、取得時に読み取りまたは書き込み操作を回避します。

解決策は次のとおりです。

  1. コンテナー クラス A にロックを追加します。このクラスは、読み取り/書き込みロックの真の所有者です。
  2. クラス B にロック属性を追加します。このクラスはロックを所有しておらず、読み取りロックするために参照するだけです。
  3. 新しい B オブジェクトを A コンテナーに追加するときは、コンテナーの ReentrantReadWriteLock への参照を B オブジェクトに提供します。A から B オブジェクトを抽出するときは、コンテナーから「継承された」ロックを逆参照します。
  4. クラス A では、B オブジェクトのコレクションを変更する各操作 (追加、削除...) の書き込みロック。必要に応じて (基になる B 要素のコレクションが同期されていない場合)、コレクションを変更せずにアクセスする読み取りロック操作。
  5. クラス B では、すべての操作で読み取りロックを行います。

コード。これはテストされていませんが、実装方法のアイデアを提供します。

class A {
    private String id;
    private List<B> bs = new ArrayList<B>();
    private ReentrantReadWriteLock lk = new ReentrantReadWriteLock();

    A(String id){
        this.id = id;
    }

    public List<B> getBs(){
        lk.readLock().lock(); // acquire the read lock, since this operation does not affect A contents
        return bs;
        lk.readLock().unlock();
    }

    public void setBs(List<B> bs){
        lk.writeLock().lock(); // acquire the write lock, which automatically avoids further reading/writing operations
        this.bs=bs;
        for( B elem : bs )
        {
            // internal B elements need a reference to the reading part of the lock
            elem.setLock(lk.readLock()); 
        }
        lk.writeLock().unlock();
    }

    public void addBs(List<B> bs){
        [...] // similar approach that in setBs
    }

    public void addB(B b){
        [...] // similar approach that in setBs
    }

    public void deleteB( B elem ) // Or whatever notation you want
    {            
        lk.writeLock().lock(); // acquire the write lock
        B internalElem = bs.get(bs.indexOf(elem));             
        if( internalElem != null )
        {
            bs.remove(internalElem);
            bs.unsetLock();
        }
        lk.writeLock().unlock();
    }
}

class B {
    private String id;
    private List<String> tags;
    private Lock lk;

    B(String id){
        this.id = id;
        lk = null;
    }

    public void setLock(Lock l){ lk = l; } // put additional controls if you want
    public void unsetLock()
    { 
        lk = null; 
    }

    private void lockInternal()
    {
        if(lk!=null){ lk.lock(); }
    }

    private void unlockInternal()
    {
        if(lk!=null){ lk.unlock(); }
    }

    public List<String> getTags(){
        List<String> ref = null;            
        lockInternal();
            [...] //internal operations
        unlockInternal();
        return ref;
    }

    public void setTags(List<String> tags){
        [...] // similar approach that in getTags
    }

    public void addTags(List<String> tags){
        [...] // similar approach that in getTags
    }

    public void addTag(String tag){
        [...] // similar approach that in getTags
    }
}

元の答え:

Jeff の回答は、A タイプのオブジェクトに触れてその構成を変更するという問題を解決するように見えるため、良い出発点です。ただし、A に含まれる B 型オブジェクトに関連する問題がまだ残っています。内部にいくつかの B 型要素を持つ A 型オブジェクトがあるとします。

A a1 = new A("10");
B b1 = new B("myB1");
B b2 = new B("myB2");
B b3 = new B("myB3");
a1.add(b1);
a1.add(b2);
a1.add(b3);

問題は、 を操作している場合、 、 、およびa1の操作をロックしたいということです。どうすればそれを確認できますか?b1b2b3

私が見る唯一の解決策は、A コンテナー内のすべての B 要素がいくつかの共通の Lock 変数を共有することです。に対して書き込み関連の操作を呼び出すときにa1、ロックを取得し、B 要素に対する ANY 操作を回避します。B 要素に対する操作は、実際にロックを取得する必要がないことに注意してください... A タイプのコンテナーによって取得されたかどうかを確認するためだけです。

多くの欠点/考慮事項:

  • パフォーマンス: B オブジェクトに対するすべての操作には、ロックのチェックが含まれます (これは非常にコストがかかる可能性があります)。
  • 操作上の制約: 単一の B オブジェクトを複数の A タイプのコンテナーに追加できる場合、これはさらに複雑になります。
  • 実装の複雑さ: B オブジェクトを 内a1に挿入する場合、B に共有ロックを提供する必要があります。抽出時に、ロックを逆参照する必要があります。コンテナ A が削除されるとどうなりますか? そういったことに気をつけなければなりません。

EDIT:そして、このメカニズムは、Aタイプのコンテナを書くときに、含まれているBタイプの要素に対する進行中の操作を「停止」したり、考慮したりしないことを忘れていました。

于 2012-10-22T14:09:52.200 に答える