ソース|1本の花を併発するCopyOnWriteArrayList

4152 ワード

CopyOnWriteArrayListの設計思想は非常に簡単であるが、設計面ではいくつかの小さな問題に注意しなければならない.
JDKバージョン:oracle java 1.8.0_102
本当は書きたくなかったのですが、github上のCopyOnWriteArrayListのcode resultsも165 kあるので、流れのために書いておきましょう.

インプリメンテーション


二つの方法を見ればわかるよ.

読み出し要素set()

public E get(int index) {
    return get(getArray(), index);
}
final Object[] getArray() {
    return array;
}

get()メソッドは内部のgetArray()メソッドを直接呼び出し、getArray()メソッドはメンバー変数arrayを直接返します.
直接アクセスするのではなく、なぜもう1階をカプセル化するのか分かりません.
Arrayは、CopyOnWriteArrayListの内部データ構造である配列を指します.
private transient volatile Object[] array;

黒板を叩く!!!
Arrayは、Happends-Before関係を持つ読み書き操作を持つvolatile変数です.具体的には、スレッドW 1がset()メソッドによって集合を「修正」すると、スレッドR 1は直ちにget()メソッドによってarrayの最新値を得ることができる.
volatile変数の読み取り、書き込みは原子であると理解できますが、volatile、ロックなどの偏序関係を持つ操作を順序や可視性の観点から理解してほしいです.volatileの原理と使い方はvolatileキーワードの役割、原理を参照してください.

書き込み要素set()


ポイントはset()メソッドです.
public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
final void setArray(Object[] a) {
    array = a;
}

set()メソッドも簡単で、2つのポイントがあります.
  • ロックロック保護キュー修正プロセス
  • は、レプリカ上で変更され、array参照
  • に置き換えられる.
    独占ロックの考え方では,書き込みスレッドにロックをかけるだけではだめであり,読み取り,書き込みスレッドの競争問題がある.でもget()には鍵がかかっていないのに、なぜ問題ないのでしょうか?
    ロックをかけることで、同じ時間に最大1つの書き込みスレッドW 1だけがtry blockに入ることを保証する.設定する値が古い値と異なるとします.9-10行はまずデータをコピーし(この場合、try block修正セットに入る他の書き込みスレッドはありません)、11行はコピー上で対応する要素を修正し、12行はarray参照を変更します.arrayはvolatile変数なので、書かれた最新値は他のリードスレッド、ライトスレッドに表示されます.
    これがいわゆる「 」です.

    その他の質問


    15行volatile書き込みの役割


    実際、15行のvolatileは余計に書かれています.これはただコードからvolatileが書いた意味を理解するために、何かを保証する必要はありません--しかし、このような考えも適切ではなく、かえってコードを惑わせます.同様の例はaddIfAbsent()です.
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    

    基本思想は同じで、17、19行はすべて直接帰って、余分な“volatileが書きます”をしていません.
    ネットで検索すると、他にもいろいろな観点があります.もし私の観点が間違っていると思ったら、交流を歓迎します.
    addIfAbsentの符号化スタイルはset()と大きく異なり,一人で書いたものとは思えない.JDKは発展し、変化した製品であり、1つのパッケージ、さらには1つのクラスが同じ人ではなく、同じ時間に書かれた可能性があり、コードスタイル、設計思想が変化する可能性があることを認識する必要があります.更にJDKの実現が必ず正しいと仮定するのではなく(もちろん、ほとんどの場合は正しい)、正しい論理に基づいて分析し、判断しなければならない.

    なぜsetにロックをかけなければならないのですか?


    TODO 20171024
    setにロックをかけなければ、同時性能が高く、一貫性もあまり弱まっていないように見えます.未解決、交流を歓迎します.

    せっけい思想


    最後にCopyOnWriteArrayListの設計思想をまとめた.
  • は、「配列要素の参照」への同時アクセスの代わりに、「配列コピーの参照」への同時アクセスを使用し、スレッドのセキュリティを維持する難しさを大幅に低減します.
  • 現在のレプリカは失効する可能性がありますが、ある瞬間のスナップショット(ある程度不変性を満たす)に集合し、弱い一貫性を満たすに違いありません.

  • 本リンク:ソース|一枝の花を併発するCopyOnWriteArrayList作者:サル007出典:https://monkeysayhi.github.io本文は知識共有署名-同方式共有4.0国際許可協定の発表に基づいて、転載、演繹または商業目的に使用することを歓迎するが、本文の署名とリンクを保留しなければならない.