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