线程安全与不安全集合
线程不安全集合:
- 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
extends AbstractSet implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList al; /** * Creates an empty set. */ public CopyOnWriteArraySet() { // 构造器内部实例化了一个CopyOnWriteArrayList al = new CopyOnWriteArrayList (); } // ... }
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();
}
}
}