ArrayList がスレッドセーフではない理由と 3 つの解決策 [詳細]

安全でない理由

  • ArrayList のソース コードを確認すると、add メソッドが見つかります。
public boolean add(E e) {
    
    
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

上記のコードからわかるように、add() メソッドは同期ミューテックスを使用しないため、マルチスレッドの同時実行ではスレッド例外が発生します。テスト コード:

import java.util.ArrayList;
import java.util.UUID;

public class SetUnsefertyTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建ArrayList 集合
        ArrayList<String> list = new ArrayList<>();

        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

異常な:
画像の説明を追加してください

解決

ベクター

Vector の追加ソース コードを参照すると、synchronized キーワードが追加されますが、
Vector はあまり使用されません。追加された要素がロックされるたびに、重いロック synchronized が使用され、リソースが消費され、非常に負荷がかかるためです。非効率的な。使い方は ArrayList と同じです。

public synchronized boolean add(E e) {
    
    
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

テストコード:

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

public class SetUnsefertyTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建ArrayList 集合
        List<String> list = new Vector<String>();

        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

コレクション

Collections の最下層に入り、synchronizedList(List list) メソッドを見つけます。ソース コードは次のとおりです。synchronizedList(List list) メソッドは、指定されたリストによってサポートされる同期された (スレッドセーフな) リストを返します。

public static <T> List<T> synchronizedList(List<T> list) {
    
    
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

static <T> List<T> synchronizedList(List<T> list, Object mutex) {
    
    
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list, mutex) :
            new SynchronizedList<>(list, mutex));
}

コレクションの使用方法は次のとおりです

List<String> list = Collections.synchronizedList(new ArrayList<>());

テストコード:

import java.util.*;

public class SetUnsefertyTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建ArrayList 集合
//        List<String> list = new Vector<String>();

        List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList

これはコピーオンライトの考え方です。まず、add() メソッドのリエントラント ロックに注目してください。この目的は、複数のスレッドが書き込み権をめぐって競合するのを防ぐことです。次に、下の赤枠の内容を確認します。オリジナルをコピーし、コピーに書き込み、setArray() メソッドを通じてオリジナルのアドレスがコピーを指すようにすることで、誰もがオリジナルを読み取ることができ、私はコピーのみを変更します。読み取りと書き込みの間に競合がないため、ロックとコピーオンライトを使用します。このアイデアでは、マルチスレッドの場合にすべてのスレッドが読み取り可能であることを十分に保証できますが、書き込みを行うのは 1 つのスレッドのみであるため、同時変更例外は発生しません。 、ソースコードなど:

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();
    }
}

テストコード:

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class SetUnsefertyTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建ArrayList 集合
//        List<String> list = new Vector<String>();

//        List<String> list = Collections.synchronizedList(new ArrayList<>()) ;
        List<String> list =new CopyOnWriteArrayList<>() ;
        // 创建10个线程,往 list 中添加元素
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(()->{
    
    
                // 向集合中添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                // 从集合中取出内容
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

3 つの解決策のまとめ

3 つを比較すると、Vector と Collections でも同期を実現できますが、これら 2 つのメソッドは下部で同期された重量ロックを使用するため、非常に非効率になるため、ArrayList の同期には主に CopyOnWriteArrayList が使用されます。

おすすめ

転載: blog.csdn.net/weixin_54046648/article/details/128174145
おすすめ