話をしながら、(9)JavaのCopy-On-Write容器について話します。
4347 ワード
Copy-On-Write略称COWは、プログラム設計における最適化戦略である。その基本的な考え方は、最初からみんなが同じ内容を共有しています。ある人がこの内容を直したいと思ったら、本当にCopyを外に出して新しい内容を形成してから変えます。これは遅延怠け者の策略です。JDK 1.5からJavaを開始し、パッケージにはCopyOWrite機構を用いて実現される2つの同時容器が提供されています。これらはCopyOnWriteArayListとCopyOnWriteAraySetです。CopyOWrite容器は非常に有用で、多くの同時シーンで使用できます。
CopyOWrite容器とは?
CopyOWrite容器は書き込み時にコピーした容器です。分かりやすいのは、私達が一つの容器に元素を添加する時、直接に現在の容器に添加しないで、先に現在の容器をCopyして、新しい容器を複製して、新しい容器に元素を追加して、元素を追加した後、元の容器の引用を新しい容器に指します。このようにするメリットは、CopyOWrite容器を同時に読むことができます。ロックをかける必要はありません。現在の容器には何の元素も添加されていません。だからCopyOWrite容器も読み书きと书き分けの思想で、読み书きと书き方が违う容器です。
CopyOWriteArayListの実現原理
CopyOWriteArayListを使う前に、まずそのソースコードを読んで、どうやって実現されたのかを確認します。以下のコードはArayListに元素を添加するもので、添加する時はロックをかける必要があります。そうでないとマルチスレッドで書く時はCopyがN個のコピーを出します。
CopyOWriteの応用シーン
CopyOnWriteを併発した容器は、読み書きの少ない併発シーンに使用されます。例えば、ホワイトリスト、ブラックリスト、商品類目の訪問と更新シーンがあります。もし検索サイトがあれば、ユーザーはこのウェブサイトの検索ボックスにキーワードを入力して内容を検索しますが、いくつかのキーワードは検索できません。これらの検索できないキーワードはブラックリストに入れられます。ブラックリストは毎晩更新されます。ユーザーが検索すると、現在のキーワードがブラックリストにないことを確認します。もしあるなら、ヒントは検索できません。実現コードは以下の通りです。は、拡張オーバヘッドを低減する。実際の必要に応じて、CopyOWriteMapのサイズを初期化し、CopyOnWriteMapの拡大支出を避ける。 は大量添加を使用する。添加するたびに、容器は毎回複製するので、添加回数を減らし、容器の複製回数を減らすことができます。上のコードの中のaddBlackList方法を使うなら。 CopyOWriteの欠点
CopyOWrite容器には多くの利点がありますが、同時にメモリの占有問題とデータの整合性の問題があります。だから開発には注意が必要です。
メモリ占有問題
CopyOWriteの書き込み時にコピーする仕組みなので、書き込み操作を行う際には、メモリには2つのオブジェクトのメモリが同時に存在し、古いオブジェクトと新しい書き込みの対象が同時に存在します。対象メモリが二つあります。これらのオブジェクトが占有するメモリが比較的大きい場合、例えば200 M前後であれば、さらに100 Mのデータを書き込み、メモリは300 Mを占めます。この場合、頻繁にYong GCとフルGCが発生する可能性があります。前のシステムではCopyOWriteを使って夜毎に大きなオブジェクトを更新し、毎晩15秒のフルGCをもたらしました。応用応答時間も長くなりました。
メモリの占有問題に対しては、大きなオブジェクトのメモリ消費を圧縮する方法により、例えば、要素が全部10進の数字であれば、36進または64進に圧縮することが考えられます。CopyOnWrite容器を使わずに、他のコンカレントHashMapなどのコンカレント容器を使用します。
データ整合性の問題
CopyOWrite容器はデータの最終的な一致だけを保証して、データのリアルタイムの一致性を保証することができません。ですから、書き込むデータが欲しいなら、すぐ読めます。CopyOWrite容器を使わないでください。
CopyOWrite容器とは?
CopyOWrite容器は書き込み時にコピーした容器です。分かりやすいのは、私達が一つの容器に元素を添加する時、直接に現在の容器に添加しないで、先に現在の容器をCopyして、新しい容器を複製して、新しい容器に元素を追加して、元素を追加した後、元の容器の引用を新しい容器に指します。このようにするメリットは、CopyOWrite容器を同時に読むことができます。ロックをかける必要はありません。現在の容器には何の元素も添加されていません。だからCopyOWrite容器も読み书きと书き分けの思想で、読み书きと书き方が违う容器です。
CopyOWriteArayListの実現原理
CopyOWriteArayListを使う前に、まずそのソースコードを読んで、どうやって実現されたのかを確認します。以下のコードはArayListに元素を添加するもので、添加する時はロックをかける必要があります。そうでないとマルチスレッドで書く時はCopyがN個のコピーを出します。
public boolean add(T 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();
}
}
final void setArray(Object[] a) {
array = a;
}
読む時はロックは必要ありません。読む時に複数のスレッドがアラーリストにデータを追加しています。読む時は古いデータを読みます。書く時は古いアラーリストをロックしません。public E get(int index) {
return get(getArray(), index);
}
JDKではCopyOWriteMapは提供されていません。CopyOnWriteArayListを参照して実現できます。基本コードは以下の通りです。import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {
private volatile Map<K, V> internalMap;
public CopyOnWriteMap() {
internalMap = new HashMap<K, V>();
}
public V put(K key, V value) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
V val = newMap.put(key, value);
internalMap = newMap;
return val;
}
}
public V get(Object key) {
return internalMap.get(key);
}
public void putAll(Map<? extends K, ? extends V> newData) {
synchronized (this) {
Map<K, V> newMap = new HashMap<K, V>(internalMap);
newMap.putAll(newData);
internalMap = newMap;
}
}
}
実現はとても簡単で、CopyOWriteのメカニズムを知る限り、様々なCopyOnWrite容器を実現し、異なるアプリケーションシーンで使用することができます。CopyOWriteの応用シーン
CopyOnWriteを併発した容器は、読み書きの少ない併発シーンに使用されます。例えば、ホワイトリスト、ブラックリスト、商品類目の訪問と更新シーンがあります。もし検索サイトがあれば、ユーザーはこのウェブサイトの検索ボックスにキーワードを入力して内容を検索しますが、いくつかのキーワードは検索できません。これらの検索できないキーワードはブラックリストに入れられます。ブラックリストは毎晩更新されます。ユーザーが検索すると、現在のキーワードがブラックリストにないことを確認します。もしあるなら、ヒントは検索できません。実現コードは以下の通りです。
package com.king.book;
import java.util.Map;
import com.king.book.forkjoin.CopyOnWriteMap;
/**
*
*/
public class BlackListServiceImpl {
private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(1000);
public static boolean isBlackList(String id) {
return blackListMap.get(id) == null ? false : true;
}
public static void addBlackList(String id) {
blackListMap.put(id, Boolean.TRUE);
}
/**
*
*
* @param ids
*/
public static void addBlackList(Map<String,Boolean> ids) {
blackListMap.putAll(ids);
}
}
コードは簡単ですが、CopyOWriteMapを使うには二つのことに注意が必要です。CopyOWrite容器には多くの利点がありますが、同時にメモリの占有問題とデータの整合性の問題があります。だから開発には注意が必要です。
メモリ占有問題
CopyOWriteの書き込み時にコピーする仕組みなので、書き込み操作を行う際には、メモリには2つのオブジェクトのメモリが同時に存在し、古いオブジェクトと新しい書き込みの対象が同時に存在します。対象メモリが二つあります。これらのオブジェクトが占有するメモリが比較的大きい場合、例えば200 M前後であれば、さらに100 Mのデータを書き込み、メモリは300 Mを占めます。この場合、頻繁にYong GCとフルGCが発生する可能性があります。前のシステムではCopyOWriteを使って夜毎に大きなオブジェクトを更新し、毎晩15秒のフルGCをもたらしました。応用応答時間も長くなりました。
メモリの占有問題に対しては、大きなオブジェクトのメモリ消費を圧縮する方法により、例えば、要素が全部10進の数字であれば、36進または64進に圧縮することが考えられます。CopyOnWrite容器を使わずに、他のコンカレントHashMapなどのコンカレント容器を使用します。
データ整合性の問題
CopyOWrite容器はデータの最終的な一致だけを保証して、データのリアルタイムの一致性を保証することができません。ですから、書き込むデータが欲しいなら、すぐ読めます。CopyOWrite容器を使わないでください。