-1

CopyOnWriteArrayList はほとんど私が望む動作をしており、不要なコピーが削除された場合、まさに私が探しているものになります。特に、ArrayList の最後に行われた追加については、ArrayList とまったく同じように動作する可能性があります。つまり、実際に毎回新しいコピーを作成する理由はなく、無駄が多くなります。実質的に ArrayList の末尾を制限してリーダーのスナップショットをキャプチャし、新しい項目が追加された後に末尾を更新することができます。

多くのアプリケーションでは、最も一般的なタイプの追加は ArrayList の末尾に追加されるため、この機能強化は価値があるように思われます。

また、追加時にしかコピーできず、サイズ変更が必要かどうかを確認する必要があるため、追加のオーバーヘッドもありません ArrayList はとにかくこれを行う必要があります。

  1. 最後に追加するための不必要なコピーなしでこの動作を行う代替の実装またはデータ構造はありますか (つまり、スレッドセーフで、書き込みがリストの最後に追加されるだけで頻繁な読み取りを許可するように最適化されています)?

  2. CopyOnWriteArrayList の末尾に追加するコピーを削除するために Java 仕様の変更を要求する変更要求を送信するにはどうすればよいですか (サイズ変更が必要な場合を除く)。

私は、独自のカスタム コードを維持して使用するのではなく、コア Java ライブラリでこれが変更されることを本当に望んでいました。

4

4 に答える 4

4

BlockingDeque、特に を探しているようですねArrayBlockingQueue

またConcurrentLinkedQueue、「ウェイトフリー」アルゴリズム (別名ノンブロッキング) を使用するため、多くの状況で高速になる可能性がある も必要になる場合があります。これはQueue( ではなくDequeue) にすぎないため、コレクションの先頭でのみ挿入/削除できますが、ユースケースには適しているように思えます。しかし、待機のないアルゴリズムと引き換えに、内部で配列ではなくリンク リストを使用する必要があります。つまり、メモリが増え (項目をポップするときのガベージも含まれます)、メモリの局所性が低下します。ウェイトフリー アルゴリズムは、比較と設定(CAS) ループにも依存しています。つまり、「通常の」ケースでは高速ですが、競合が多い場合は実際には遅くなる可能性があります。それは勝ち、前進することができます。

私の推測では、リストがあまり愛されていない理由はjava.util.concurrent、リストが他の反復のほとんどのユースケースで本質的に際どいデータ構造であるためです。たとえば、のようなものif (!list.isEmpty()) { return list.get(0); }は、ブロックで囲まれていない限り際どいsynchronizedです。その場合、本質的にスレッドセーフな構造は必要ありません。あなたが本当に必要としているのは、最後の操作だけを許可する「リスト型」のインターフェースQueueですDeque

于 2014-01-08T22:36:02.587 に答える
3

毎回新しいコピーを実際に作成する理由はありませんが、これは非常に無駄です。

これが仕組みです。これは、比較およびスワップ アクションで以前の配列を新しい配列に置き換えることによって機能します。エントリを置き換えるだけの場合でも、常に新しい配列を使用できるようにすることは、スレッド セーフ設計の重要な部分です。

スレッドセーフで最適化されており、頻繁な読み取りを許可し、書き込みはリストの最後に追加するだけです

これは読み取り用に大幅に最適化されています。他のソリューションは、書き込みの場合は高速になりますが、読み取りの場合は遅くなり、本当に必要なものを決定する必要があります。

両方の世界で最高のカスタム データ構造を持つことができますが、それはもはや CopyOnWriteArrayList と ArrayDeque が提供する一般的なソリューションではありません。

CopyOnWriteArrayList の末尾に追加するコピーを削除するために Java 仕様の変更を要求する変更要求を送信するにはどうすればよいですか (サイズ変更が必要な場合を除く)。

バグ データベースを介してこれを行うことができますが、あなたが提案しているのは、データ構造の仕組みの根本的な変更です。あなたが望むように機能する新しい/異なるデータ構造を提案することをお勧めします。それまでの間、私はそれを実際の例として自分で実装することをお勧めします。

これは、必要な低レベルのアクションを実行するために使用できるため、AtomicReferenceArray から始めます。唯一の問題は、サイズを変更できないため、必要な最大サイズを決定する必要があることです。

于 2014-01-08T22:37:19.023 に答える
3

質問に答えるには:

  1. 完全に機能するリストである代替実装については知りません。

  2. あなたのアイデアが本当に実行可能である場合、私はいくつかの方法を考えることができます:

    • Java Bugs Database を介して「拡張要求」(RFE) を送信できます。ただし、この場合、あなたが肯定的な反応を得るとは思えません。(確かに、タイムリーではありません!)

    • Guava または Apache Commons の問題トラッカーで RFE 問題を作成できます。これは、彼らを納得させるかどうかにかかっていますが、より実り多いかもしれません...

    • あなたのアイデアを実装したパッチを OpenJDK チームに提出することができます。結果がどうなるかはなんとも言えませんが…

    • それぞれの問題トラッカーを介して Guava または Apache Commons に (上記のように) パッチを送信できます。これは成功する可能性が最も高いアプローチですが、技術的に健全であり、「良いこと」であることを「彼ら」に納得させるかどうかにかかっています。

    • 提案した代替実装のコードを Github に置くだけで、何が起こるかを確認できます。


ただし、これはすべて、アイデアが実際に機能することを前提としています。あなたが提供した乏しい情報に基づいて、私は疑わしい. 不完全なカプセル化、同時実行性、および/または List 抽象化を完全に/正しく実装していないという問題があると思われます。

コードを Github に置くことをお勧めします。そうすれば、他の人がコードをよく見ることができます。

于 2014-01-08T22:55:01.313 に答える
0

CopyOnWriteArrayList には、書き込み操作時にリストの基になる配列のコピーが作成されるため、パフォーマンス上の欠点があります。配列のコピーにより、書き込み操作が遅くなります。おそらく、Read レートが高く、Write レートが低い List の使用には、CopyOnWriteArrayList が有利です。

最終的に、java.util.concurrent.locks,ReadWriteLock を使用して独自の実装のコーディングを開始しました。オブジェクト レベルの ReadWriteLock インスタンスを維持し、読み取り操作で読み取りロックを取得し、書き込み操作で書き込みロックを取得するだけで実装を行いました。コードは次のようになります。

public class ConcurrentList< T > implements List< T >
{
    private final ReadWriteLock  readWriteLock = new ReentrantReadWriteLock();
    private final List< T > list;

    public ConcurrentList( List<T> list )
    {
        this.list = list;
    }

    public boolean remove( Object o )
    {
        readWriteLock.writeLock().lock();
        boolean ret;
        try
        {
            ret = list.remove( o );
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
        return ret;
    }

    public boolean add( T t )
    {
        readWriteLock.writeLock().lock();
        boolean ret;
        try
        {
            ret = list.add( t );
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
        return ret;
    }

    public void clear()
    {
        readWriteLock.writeLock().lock();
        try
        {
            list.clear();
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
    }


    public int size()
    {
        readWriteLock.readLock().lock();
        try
        {
            return list.size();
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

    public boolean contains( Object o )
    {
        readWriteLock.readLock().lock();
        try
        {
            return list.contains( o );
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

    public T get( int index )
    {
        readWriteLock.readLock().lock();
        try
        {
            return list.get( index );
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

//etc
}

観察されたパフォーマンスの改善は顕著でした。

10 スレッドによる 5000 回の読み取り + 5000 回の書き込み (読み取りと書き込みの比率は 1:1) にかかった合計時間は、

  • ArrayList - 16450 ns (スレッドセーフではない)
  • ConcurrentList - 20999 ns
  • ベクトル -35696 ns
  • CopyOnWriteArrayList - 197032 ns

上記の結果を取得するために使用されるテスト ケースの詳細については、このリンクをたどってください。

ただし、Iterator を使用するときに ConcurrentModificationException を回避するために、現在の List のコピーを作成し、その反復子を返しました。これは、このリストが戻らないことを意味し、元のリストを変更できる Iterator です。まあ、私にとっては、これは今のところ大丈夫です。

public Iterator<T> iterator()
    {
        readWriteLock.readLock().lock();
        try
        {
            return new ArrayList<T>( list ).iterator();
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

グーグルで調べた後、元のリストを変更できるイテレータを返さないため、CopyOnWriteArrayList にも同様の実装があることがわかりました。Javadoc によると、

返された反復子は、反復子が構築されたときのリストの状態のスナップショットを提供します。イテレータをトラバースしている間、同期は必要ありません。イテレータは remove メソッドをサポートしていません。

于 2014-11-24T09:53:47.733 に答える