期待の高かった自分に追いつくために走り続けました。-リビングストン
0序文
私たちは、ArrayListの非スレッドセーフは、あなたがロック所有または使用する必要があることを知っているCollections.synchronizedList
COWを呼び、CopyOnWriteArrayListと- 。包装容器は、リストのコピーオンライトJDK1.5開始JUCを使用してから達成するために同時実行スレッドセーフなメカニズムを提供します
1 CopyOnWriteデザインアイデア
1.1基本的な概念
CopyOnWrite copy-on-write。一般的に言えば、コンテナーに要素を追加する場合、現在のコンテナーに直接追加するのではなく、まず現在のコンテナーを新しいコンテナーからコピーし、新しいコンテナーに要素を追加し、要素を追加します。次に、元のコンテナを新しいコンテナにポイントします。つまり、最初は全員が同じコンテンツを共有しています。誰かがコンテンツを変更したい場合、実際にコンテンツをコピーして新しいコンテンツを作成し、それを変更します。これは一種です遅延戦略を遅らせます。
1.2設計の利点
現在のコンテナーは要素を追加しないため、CopyOnWriteコンテナーはロックせずに同時に読み取ることができます。したがって、これは別のコンテナーの読み取りと書き込みのアイデアでもあります。
2継承体系
- ArrayListの継承システムに似ています
3物件
- すべての修飾子を保護するためのロック
- getArray / setArrayを介してのみアクセスできる配列
- ロックメモリオフセット
4工法
4.1パラメータなし
- 空のリストを作成する
4.2参加
- 指定されたコレクションの要素を含むリストを作成します。その順序は、コレクションの反復子によって返されます。
- 指定された配列のコピーを保持するリストを作成します
ソースコードと、それを書き込み時にコピーする方法を見てみましょう。
5追加(Eおよび)
COWに要素を追加するにはロックが必要です。ロックしないと、同時書き込み中にN個のコピーがコピーされます。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 1.加锁
lock.lock();
try {
// 得到原数组
Object[] elements = getArray();
int len = elements.length;
// 2.复制出新数组,加一是因为要添加yi'ge'yuan's
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 把新元素添加到新数组里,直接放在数组尾部
newElements[len] = e;
// 把原数组引用指向新数组
setArray(newElements);
return true;
} finally {
// finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁
lock.unlock();
}
}
复制代码
getArray
- 配列を取得します。非公開ではないため、CopyOnWriteArraySetクラス(メンバー変数としてCopyOnWriteArrayListを直接組み合わせています)からもアクセスできます。
setArray
- 新しい配列への参照を設定します
両方がロックされていますが、元の配列を直接変更せずに配列をコピーする必要があるのはなぜですか?
- volatileは配列参照によって変更されます!元の配列のいくつかの要素の値を変更するだけです。この操作は表示には使用できません。配列のメモリアドレスを変更する必要があります
- 新しい配列でcopyOfを実行しても、元の配列には影響しません。新しい配列が完全にコピーされた後にのみ、外部からアクセスでき、元の配列のデータ変更による悪影響を回避できます。
6ゲット
get(intインデックス)
- 指定された位置要素を読み取ります
get(Object [] a、int index)
読み取り時にロックを追加する必要はありません。読み取り中に他のスレッドがArrayListにデータを追加している場合、古い配列は書き込み時にロックされないため、読み取りは古いデータのみを読み取ります。
7削除
7.1指定したインデックスを削除する
public E remove(int index) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 先得到旧值
E oldValue = get(elements, index);
int numMoved = len - index - 1;
// 如果要删除的数据正好是数组的尾部,直接删除
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
// 若删除的数据在数组中间:
// 1. 设置新数组的长度减一,因为是减少一个元素
// 2. 从 0 拷贝到数组新位置
// 3. 从新位置拷贝到数组尾部
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
复制代码
さらに3つの軸:
- ロックする
- インデックスが削除される場所に応じて、異なるポリシーのコピーを作成します
- ロックを解除
7.2一括削除
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// newlen 表新数组的索引位置,新数组中存在不包含在 c 中的元素
int newlen = 0;
Object[] temp = new Object[len];
// 循环,把不包含在 c 里面的元素,放到新数组中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
// 不包含在 c 中的元素,从 0 开始放到新数组中
if (!c.contains(element))
temp[newlen++] = element;
}
// 拷贝新数组,变相的删除了不包含在 c 中的元素
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
复制代码
配列要素を1つずつ直接削除するのではなく、まず配列の値をループで判断し、削除する必要のないデータを一時配列に入れます。最後に、一時配列のデータは削除する必要のないデータです。
8まとめ
CopyOnWrite同時実行コンテナーは、読み取り数が多く、書き込み数が少ない並行シナリオに適しています。CopyOnWriteコンテナーには多くの利点がありますが、問題もあります。開発時には注意が必要です。
メモリ使用量の問題
書き込み時には、2つのオブジェクトのメモリが同時にメモリに常駐します。古いオブジェクトと新しく書き込まれたオブジェクト(コピー時にコンテナ内の参照のみがコピーされますが、書き込み時に新しいオブジェクトが作成されて新しいコンテナに追加されます。コンテナのオブジェクトはまだ使用されているため、オブジェクトメモリのコピーが2つあります。これらのオブジェクトが大量のメモリを占有している場合、GCが頻繁に発生し、アプリケーションの応答時間が長くなる可能性があります。メモリ、またはCopyOnWriteコンテナを直接使用しないが、ConcurrentHashMapなどの他の同時コンテナを使用する。
データの整合性の問題
コピーオンライトコンテナはデータがあることのみを保証することができ最终一致性
、データは保証できません实时一致性
、してください使用の裁量を。