1. 程式人生 > 實用技巧 >Java集合多執行緒安全

Java集合多執行緒安全

執行緒安全與不安全集合

執行緒不安全集合:

  • ArrayList
  • LinkedList
  • HashMap
  • HashSet
  • TreeMap
  • TreeSet
  • StringBulider

執行緒安全集合:

  • Vector
  • HashTable
  • Properties

集合執行緒安全與解決方案

ArrayList執行緒安全問題

package com.raicho.mianshi.mycollection;

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

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 30; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            }).start();
        }
    }
}

執行報錯:


ArrayList是執行緒不安全的,add()方法並沒有加鎖(synchronized),多執行緒環境下會丟擲ConcurrentModificationException

解決方案:

  • 使用Vector類(使用了synchronized),效率極低

  • 使用Collections.synchronizedList(new ArrayList<>()):內部直接將接受的List物件傳遞給靜態內部類SynchronizedList物件,然後Collections.synchronizedList(new ArrayList<>())返回的List物件的呼叫方法都是直接呼叫輸入List物件的方法,但是加了synchronized,類似裝飾器模式,也是對輸入List的一種增強:
package com.raicho.mianshi.mycollection;

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

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {

    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 50; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

原始碼:

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

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

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        synchronized (mutex) {list.replaceAll(operator);}
    }
    @Override
    public void sort(Comparator<? super E> c) {
        synchronized (mutex) {list.sort(c);}
    }

    private Object readResolve() {
        return (list instanceof RandomAccess
                ? new SynchronizedRandomAccessList<>(list)
                : this);
    }
}
  • CopyOnWriteArrayList:寫時複製是一種讀寫分離的思想,在併發讀的時候不需要加鎖,因為它能夠保證併發讀的情況下不會新增任何元素。而在併發寫的情況下,需要先加鎖,但是並不直接對當前容器進行寫操作。而是先將當前容器進行復制獲取一個新的容器,進行完併發寫操作之後,當之前指向原容器的引用更改指向當前新容器。也就是說,併發讀和併發寫是針對不同集合,因此不會產生併發異常
package com.raicho.mianshi.mycollection;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 15:32
 **/
public class ArrayListConcurrentDemo {

    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; ++i) {
            new Thread(() -> {
                list.add(UUID.randomUUID().randomUUID().toString().substring(0, 4));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

原始碼:

// CopyOnWriteArrayList.java
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();
    }
}

public E set(int index, E element) {
    // 更新操作類似
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

// 讀操作不加鎖
private E get(Object[] a, int index) {
    return (E) a[index];
}

在新增元素e完後,再呼叫setArray(newElements);函式重新賦值,之前指向原容器的引用更改指向當前新容器

HashSet執行緒安全問題

HashSet底層就是一個HashMap,預設的HashSet是一個初始大小為16,負載因子為0.75的HashMap:

HashSet的多執行緒安全問題實際上就是HashMap的多執行緒安全問題:
package com.raicho.mianshi.mycollection;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 17:03
 *
 * HashSet多執行緒不安全問題
 * HashSet底層就是HashMap,因此這個案例也是HashMap多執行緒不安全問題的演示
 */
public class HashSetThreadUnsafe {
    public static void main(String[] args) {
        Set<String> sets = new HashSet<>();
        for (int i = 0; i < 100; ++i) {
            new Thread(() -> {
                sets.add(UUID.randomUUID().toString().substring(0, 4));
                System.out.println(sets);
            },String.valueOf(i)).start();
        }
    }
}

解決方案:

  • Collections集合類的static方法SynchronizedSet
  • CopyOnWriteArraySet:也是寫時複製思想,但是內部還是使用CopyOnWriteArrayList實現:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;

    /**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        // 構造器內部例項化了一個CopyOnWriteArrayList
        al = new CopyOnWriteArrayList<E>();
    }
    // ...
}

HashMap多執行緒安全的解決方案

相比於HashSet,HashMap除了可以使用Collections集合類的synchronizedMap方法外,還可以使用juc包下ConcurrentHashMap類。

package com.raicho.mianshi.mycollection;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 17:03
 */
public class HashMapThreadUnsafe {
    public static void main(String[] args) {
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 100; ++i) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 4));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}