スレッド併発--CocurrenthashMapとCopyOnWriteArayList詳細

6505 ワード

マルチスレッドの開発において、スレッドの同時進行の問題をよく考えますが、スレッドの同時コードによるデータ読みと書きの問題を避けるにはどうすればいいですか?
私たちがよく見ているHashMap、TreeMap、LinkdList、ArayListはスレッドが安全ではないです。Javaもいくつかのスレッドの安全な容器類を提供しています。
例えば:
各種の合併容器:CocurrenthashMap、CopyOWriteArayLisなど。
各種スレッド安全キュー(Queue/Deque):ArayBlockingQueque、SyncronousQueなど。
各種の秩序化容器のスレッド安全バージョンなど。
以下では、CocurrenthashMapとCopyOnWriteArayListはどうやって効率的なスレッドセキュリティを実現しますか?
最初はjavaを勉強したばかりの時に、同時の状況があるかもしれません。データベースの読み書きのように、簡単にsynchronizedキーワードを加えればいいです。しかし、これは最も効果的な合併方式の処理であり、つまり、3721、set、getの方法に関わらず、synchronizedを加えて完成させます。どうやって効率的な同時進行ができますか?次はCocurrenthashMapソースの高効率な合併問題の解決策については、すべてのソースコードを議論しないで、スレッドの安全に関するソースコードのみです。
1.C.ocurrent HashMapの高効率な同時ソース分析
1.1 volatileキーワード
volatileキーワードを知らない人は私のコレクションのこの編を見ることができます。https://blog.csdn.net/fwt336/article/details/80986409volatileについて詳しく話しています。
スレッド合併では、volatileの可視特性を用いて、動作変数の視認性を保証し、volatileの非原子操作に対して、Cocurrent HashMapはどのようにしているかを見ることができます。
1.2 volatileの使用
次にCocurrenthashMapソースの中のvolatileの応用を見てみます。
私達はすべて知っています。ソースの中ではこのテーブル配列を通してMapの中のkeyとvalueの値を保存します。
transient volatile Node[] table;
Nodeは本当に私達のkeyとvalueの値に対するパッケージです。
static class Node implements Map.Entry {
    final int hash;
    final K key;
    volatile V val;
    volatile Node next;

    Node(int hash, K key, V val, Node next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }

    public final K getKey()     { return key; }
    public final V getValue()   { return val; }
    public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
    public final String toString() {
        return Helpers.mapEntryToString(key, val);
    }
    public final V setValue(V value) {
        throw new UnsupportedOperationException();
    }
    ...
}
Nodeの中にnextがありますが、なぜここにnextノードがありますか?それはMapの記憶の詳細についてはあまり詳しくないということです。ここはHashMapと似ています。簡単にまとめると、Mapは配列テーブルによってデータを格納しますが、テーブル配列のNodeノードはチェーンで実現されています。格納時にhash衝突が発生するため、hash換算で対応するテーブル配列のindexは同じかもしれません。配列の中で同じindexの値は複数のkeyとvalueが存在します。チェーンを通してこの問題に近づけることができます。つまりなぜHashMapは規則的に保存されていないのですか?データ量が多すぎると、チェーンの検索性能の問題が明らかになります。この時はチェーンをツリー化して性能を最適化します。
話が逸れました。戻る=======================================================================================================================
ソースのvalとnextはすべてvolatileで修飾されていることを見ました。Nodeのソースコードの中で私達もsynchronizedという同期キーワードを見ていません。get方法を見ても、synchronizedキーワードは必要ないです。volatileの視認性があるので、データの視認性を保証します。原子の操作はどこで行われますか?
1.3原子操作の保証synchronized
volatileでvalとnextを修飾した後、get方法はsynchronizedで修飾する必要がないことを知っています。このように性能が向上しました。
修正に関しては、set方法に間違いないです。
public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node[] tab = table;;) {
        Node f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node p;
                        binCount = 2;
                        if ((p = ((TreeBin)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            ...
        }
    }
    addCount(1L, binCount);
    return null;
}
    synchronize     f    ,          value。            ,        。          ,    ,      value   ,                。

2.CopyOnWriteArrayList高效并发源码分析

同样的,CopyOnWriteArrayList也使用了volatile来保证可见性,synchronized进行同步,看数组定义:

private transient volatile Object[] elements;
違いは:
final transient Object lock = new Object();
もう一つのこのゲームはロックです。
public E set(int index, E element) {
    synchronized (lock) {
        Object[] elements = getArray();
        ...
        return oldValue;
    }
}
public boolean add(E e) {
    synchronized (lock) {
        ...
        return true;
    }
}
private boolean remove(Object o, Object[] snapshot, int index) {
    synchronized (lock) {
        ...
        return true;
    }
}

CocurrenthashMapでは、synchronized同期は現在動作が必要なNodeノードであり、ここではObjectクラスのインスタンスを使用してロックの対象としています。elements配列に関するすべての動作は、まずこのロックを取得する必要があります。このようにしてスレッド同期の役割を果たした。
3.まとめ
したがって、Javaから提供されたスレッドのセキュリティクラスのソースコードを見て、効率的な同時進行を実現する方法はあります。
1.可変フィールドにvolatile修飾を使う
2.getXX方法は、synchronizedキーワードを追加する必要はありません。
3.変数に関するすべての変更操作は、操作が必要な変数をsynchronizedキーで同期するか、Objectのインスタンスをロックとして定義し、同期する