Javaのキューを知る:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList

Javaのキューを知る:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList

こんにちは、私は山を見ています。

ウェンに接続された本、複数のスレッドで最後にチャットされた使用ArrayList何が起こるか、今回は通常、共通リストについて話します:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList 。

ベクター

VectorJDK 1.0で提供されていますが、マークは付いていませんがDeprecated、実際には誰も使用していません。主な理由はパフォーマンスの低下であり、需要を満たしていません。

ソースコード(ソースコードはここに掲載されていません)からわかるようVectorに、配列の実装に基づいています。ほとんどすべての操作メソッドは、synchronizedキーワードを使用してメソッドの同期を実現します。この同期メソッドは、複数のスレッドなどの単一の操作をロックできます。同時にadd、実行は同期的に実行をブロックしますが、マルチスレッドの実行addremove時間はブロックしません。

ただし、キューをロックする必要があるシナリオのほとんどは、単一の操作だけでなく、キュー全体をロックすることです。つまり、Vector予想とは異なりますが、同期操作によって発生するパフォーマンスのオーバーヘッドも増加します。したがって、シーンを使用する必要はArrayListなく、代わりに使用できます。同期キューが必要なマルチスレッドの状況であっても、使用CopyOnWriteArrayListしてSynchronizedList置き換えることができます

配列リスト

ArrayListこれはJDK1.1で提供されています。Vector後継(ArrayList実装はVectorほぼ同じ)であるため、すべてのArrayListメソッドsynchronizedが削除され、同期がまったく行われず、スレッドセーフではありません。

その非スレッドセーフは、イテレータの急速な障害にも反映されています。メソッドiterator使用しlistIteratorてイテレータ作成した後、元のArrayListキューが変更(追加または削除)された場合、イテレータが反復しているときにConcurrentModificationException例外が報告されますソースコードから、イテレータの反復プロセス中modCountに、キュー内の変更の数が、イテレータが作成されたときに減少した変更の数のスナップショットexpectedModCountと等しいかどうかをチェックすることがわかります。コードは次のとおりです。

private class Itr implements Iterator<E> {
    
    
    // 这段代码是从 ArrayList 中摘取的
    // 只留下检查方法,略过其他代码,有兴趣的可以从源码中查看
    final void checkForComodification() {
    
    
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

3つ目のポイントは、マルチスレッドシナリオでは、要素を追加するとデータが失われたり、配列の範囲外の例外が発生したりする可能性があることです。マルチスレッドでArrayListを使用するとどうなるかについて詳しく説明しているので、ここでは詳しく説明しません。ここに。

SynchronizedList

SynchronizedListはい、静的メソッドCollectionsを使用してCollections.synchronizedList()作成された静的内部クラスは、複合Listクラスによって実現されるカプセル化された実装です。synchronized (mutex){...}ロックされたオブジェクトmutexはキューオブジェクトで定義されたオブジェクトと同じであるため、ほとんどのメソッドはコードブロック同期を使用します。したがって、mutexロックがロックされると、キュー全体がロックされ、Vectorキュー全体をロックできないという問題が解決されます。 。したがって、複数のスレッドが同時に動作しaddremoveメソッドがある場合、同期実行がブロックされます。

ArrayList以下のソースコードの注記にあるように、既存の状況でのイテレータの急速な障害は依然として存在します。イテレータを使用する場合は、手動で同期を実装する必要があります。

static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    
    
    
    // 代码摘自 Collections,省略很多代码

    public void add(int index, E element) {
    
    
        synchronized (mutex) {
    
    list.add(index, element);}
    }

    public ListIterator<E> listIterator() {
    
    
        return list.listIterator(); // Must be manually synched by user
    }

    public ListIterator<E> listIterator(int index) {
    
    
        return list.listIterator(index); // Must be manually synched by user
    }
}

手動同期に注意する必要があります。グローバル同期が懸念されるため、イテレータで同期を設定するときaddは、ロックされたオブジェクトがメソッド内のオブジェクトと同じであることを確認してください。これはフォローアップで説明されるので、ここでは拡張しません。

CopyOnWriteArrayList

CopyOnWriteArrayListJDK 1.5以降に提供されaddています。まず、メソッドのソースコードを確認しください

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    // 代码摘自 CopyOnWriteArrayList,省略很多代码

    public boolean add(E e) {
    
    
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
    
    
            lock.unlock();
        }
    }

    public boolean addAll(Collection<? extends E> c) {
    
    
        Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
            ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
        if (cs.length == 0)
            return false;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            Object[] elements = getArray();
            int len = elements.length;
            if (len == 0 && cs.getClass() == Object[].class)
                setArray(cs);
            else {
    
    
                Object[] newElements = Arrays.copyOf(elements, len + cs.length);
                System.arraycopy(cs, 0, newElements, len, cs.length);
                setArray(newElements);
            }
            return true;
        } finally {
    
    
            lock.unlock();
        }
    }

    private E get(Object[] a, int index) {
    
    
        return (E) a[index];
    }

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
    
    
        return get(getArray(), index);
    }
}

ことがわかるCopyOnWriteArrayList助けを借りてReentrantLock、同期の、パフォーマンスが高いsynchronized、最適化の前にこれも配列を介して実装されますが、配列の前にキーワードが追加されます。これにより、マルチスレッドの場合に配列の可視性が実現され、より安全になります。より重要な点は、要素を追加するときの実装は、配列オブジェクトを再構築し、元の配列参照を置き換えることです。拡張方式と比較すると、スペースは削減されますが、割り当て配列のパフォーマンスオーバーヘッドも増加します。要素が取得されるとき、ロックはなく、データは直接返されます。ReentrantLocksynchronizedCopyOnWriteArrayListvolatileCopyOnWriteArrayListaddArrayListget

CopyOnWriteArrayListのイテレータがCOWIterator実装され、iteratorメソッドが呼び出されると、現在のキュー内の配列のスナップショットがイテレータ内の配列参照に割り当てられます。元のキューが変更された場合、キュー内の配列は他の参照を指し、イテレータ内の配列は変更されないため、マルチスレッド実行のプロセスでは、キュー内のデータは、イテレータを介して配列します。このようにしてスレッドセーフを確保する一方で、データの不整合も発生する可能性があり、その使用にのみ注意を払うことができます。

static final class COWIterator<E> implements ListIterator<E> {
    
    
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
    
    
        cursor = initialCursor;
        snapshot = elements;
    }
}

CopyOnWriteArrayListとSynchronizedListを比較します

CopyOnWriteArrayListSynchronizedList両方両方が同期され、実装方法で異なる戦略が採用され、それぞれの焦点が異なります。

CopyOnWriteArrayList読み取りと書き込みの分離に焦点を当てます。データ書き込み操作(addまたはremove)が発生すると、ロックされ、各スレッドが実行をブロックします。実行プロセスは、データコピーを作成し、オブジェクト参照を置き換えます。読み取り操作(getまたはiteratorと同時に、読み取り操作は古いデータを読み取り、履歴データのスナップショットまたはキャッシュされたデータになる場合があります。これにより、読み取りと書き込みが同時に発生したときにデータの不整合が発生しますが、データは最終的に整合性があります。この方法は、データベースの読み取り/書き込み分離モードとほぼ同じであり、多くの機能を比較できます。

SynchronizedList強力なデータ整合性に焦点を当てます。つまり、データ書き込み操作(addまたはremove)が発生すると、ロックが追加され、各スレッドが実行をブロックし、操作も同じロックによってブロックされますget

CopyOnWriteArrayListSynchronizedList2つの異なる問題からCopyOnWriteArrayList、より少ない読み取り、より多くのシーン、SynchronizedList読み取りと書き込みの操作効率の書き込みにおける高効率の実装は非常にバランスが取れいると結論付けることができます。より高いCopyOnWriteArrayListインターネットからテスト結果を借ります。

CopyOnWriteArrayListとSynchronizedListを比較します

記事の最後の要約

  1. synchronizedJDK 8より前のキーワードのパフォーマンスは比較的劣っています。JDK1.5の後に実装された同期コードを見ることができ、その多くがReentrantLock実装されています。
  2. マルチスレッドのシナリオでは、同期に加えて、データの可視性も考慮する必要がありますvolatileこれは、キーワードを使用して実現できます
  3. ArrayList同期操作はまったくなく、スレッドセーフではありません
  4. CopyOnWriteArrayListそしてSynchronizedList、スレッドセーフキューに属します
  5. CopyOnWriteArrayList読み取りと書き込みの分離を実現します。書き込みを減らして読み取りを増やすシナリオに適しています
  6. SynchronizedListデータは、キューのグローバルロック方法である強い整合性が必要であり、読み取り操作もロックされます
  7. Vectorイテレータトラバーサルのパフォーマンスが非常に低いだけです。グローバルロックキューを考慮しない場合、純粋な読み取り操作と個々の書き込み操作のパフォーマンスにSynchronizedList大きな違いはありません。

参照

  1. Java Vector(およびStack)クラスが廃止または非推奨と見なされるのはなぜですか?
  2. CopyOnWriteArrayListとCollections.synchronizedListのパフォーマンスの比較
  3. Collections.synchronizedList、CopyOnWriteArrayList、ベクターの紹介、ソースコード分析、パフォーマンスの比較

推奨読書

  1. 複数のスレッドでArrayListを使用する必要がある場合はどうなりますか?

こんにちは、私はKanshanです。パブリックアカウント:Kanshanのコテージ、10年前のバックエンド、Apache Storm、WxJava、Cynomysのオープンソース寄稿者。主な仕事:プログラマー、アルバイト:建築家。コードの世界で泳ぎ、ドラマでの生活を楽しんでください。

個人のホームページ:https
//www.howardliu.cn個人のブログ投稿:Javaのキューを知る:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList
CSDNのホームページ:http:
//blog.csdn.net/liuxinghao CSDNのブログ投稿:キューを知るJavaの場合:Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList

公開番号:山小屋を見る

おすすめ

転載: blog.csdn.net/conansix/article/details/113780875