線程安全與不安全集合
線程不安全集合:
- 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();
}
}
}
