1つは、ArrayListの不安定さです。
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
上記のメソッドを実行し、書き込みと読み取りを同時に行う30のスレッドを作成すると、例外ConcurrentModificationExceptionがスローされます。例外の理由は非常に単純で、ArrayListコレクションクラスはスレッドセーフではないため、複数のスレッドが読み取りと同時に書き込むと例外がスローされ、問題が発生しますか?Listコレクションの下でスレッドセーフの問題を解決するにはどうすればよいですか?ベクター?これは良い方法ですか?
2、ベクトル
リストコレクションのセキュリティ問題を解決することになると、ほとんどの学生はベクターに答えることができます。ベクターはリストコレクションのスレッドセーフ問題を確実に解決できます。ベクターがリストコレクションのスレッドセーフ問題をどのように解決するかを簡単に分析しましょう。
Vectorの最下層もArrayListのような配列を使用しており、以下のソースコードを投稿して見てください。
メソッドの追加
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
getメソッド
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
VectorでSynchronizedキーワードが使用されていることがわかります。その結果、効率が非常に低くなります。
三、同期リスト
スレッドセーフなリストと言えば、Collectionsパッケージの下のSynchronizedListについて言及する必要があります。このクラスの名前を見ると、これがSynchronizedがListコレクションに追加されたことがわかります。
このクラスのいくつかのメソッドを見てください
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {
return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {
return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {
return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {
return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {
list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {
return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {
return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {
return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {
return list.addAll(index, c);}
}
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
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
}
SynchronizedListはListインターフェースを実装していることがわかります。つまり、拡張可能です。Listオブジェクトを渡すことができます。ただし、上記のメソッドから、同期ロックが各addおよびgetメソッドに追加されていることがわかります。 SynchronizedListの効率がそれほど高くないため、スレッドセーフを実現する方法
公式文書に記載
List list = Collections.synchronizedList(new ArrayList());
...
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext()){
}
foo(i.next());
}
奇妙なことに、Synchronizedはスレッドセーフなので、反復するときにロックのレイヤーを追加するのはなぜですか?SynchronizedListの実装コードでは、iterator()は再度カプセル化されません。Synchronizedは、読み取り操作にイテレーターを使用すると、同時実行性が高い場合にスレッドセーフが達成されないため、Synchronizedを使用する場合は手動で追加する必要があります。 。
したがって、SynchronizedListは、高性能を必要とせず、Iteratorを使用しない状況に適しています。
SynchronizedListとVectorの主な違いは次のとおりです。
- Vectorは元の長さの2倍に拡張され、ArrayListは元の長さの1.5倍に拡張されます
- SynchronizedListには、非常に優れた拡張機能と互換性機能があります。彼はすべてのリストサブクラスをスレッドセーフクラスに変換できます。
- SynchronizedListを使用する場合、トラバース時に手動で同期を実行する必要があります。
- SynchronizedListは、ロックされたオブジェクトを指定できます。
4、CopyOnWriteArrayList
VectorとSynchronizedListのパフォーマンスはそれほど高くないので、高いパフォーマンスはありますか?CopyOnWriteArrayListのパフォーマンスは、書き込みを減らして読み取りを増やすという特定のケースでは、上記の2つのコレクションよりもはるかに高くなります。
CopyOnWriterArrayListはjava.util.concurrentパッケージの下にあり、同時実行性の高い環境でJDKによって提供される高性能リストです。CopyOnWriterArrayListは、実際には読み取りと書き込みの分離を考慮したものです。CopyOnWriterArrayListを追加すると、最初にコンテナーがコピーされ、コンテナーに要素を追加した後、元のコンテナーの参照が新しいコンテナーに戻されます。このアプローチの利点読み取り時にロックする必要がなく、高い同時実行性で読み取ることができます
ソースコードの追加を見てみましょう
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array
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();
}
}
private E get(Object[] a, int index) {
return (E) a[index];
}
ソースコードは非常に明確に記述されており、最初に現在の配列を取得してから、ArraysのcopyOfメソッドを使用して現在の配列をコピーします。これはローカル変数であるため、現在のスレッドのワークスペースにのみ存在するため、コピーされたコンテナはスレッドセーフである必要があり、要素を追加してから、新しいコンテナのアドレスを元のコンテナに追加します
getメソッドはインデックスを使用してデータを返すため、書き込みがロックされている間、CopyOnWriteArrsyListの読み取りを同時に実行できます。したがって、この種のコンテナは、読み取りが多く、書き込みが少ない場合に非常に適しています。
注意が必要な詳細がもう1つあります。CopyOnWriteArrayListの配列はVolatileによって変更されます。これは、時間内の可視性を確保することを目的としています。そうしないと、新しい配列にコピーした後、他のスレッドがそれを見ることができず、面倒です。
一般に、CopyOnWriteArrayListは、読み取りと書き込みを分離するという考え方です。つまり、読み取りと書き込みは同じコンテナー内にありません。これは、データのリアルタイムの一貫性を保証するものではありませんが、の最終的な一貫性を保証することはできます。データであり、書き込みの読み取りにのみ適しています。書き込みが多すぎると、新しい配列をコピーすると大量のメモリが消費されます。