ArrayList是线程不安全的,因为在他的 add 方法添加元素时,为了保证并发性,没有加锁。
public class CollectionTest { public static void main(String[] args) { final List<String> list = new ArrayList<>(); for (int i = 0; i <20; i++) { //创建20个线程 new Thread(new Runnable() { @Override public void run() { //list添加随机字符串 list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } } - 打印结果-- - -- - - - - - - - java.util.ConcurrentModificationException 高并发下的修改异常解决方案一:用 Vector 代替 ArrayList ,Vector相较于ArrayList是线程安全的,Vector 所有针对集合内元素的操作方法都是由 synchronized 修饰的。但是!!Vector 是JDK1.0引入的,ArrayList是1.2引入的。为何有线程安全的还要新出一个不安全的?
Vector 的缺点:由于加了synchronized,导致失去了并发性,效率大大下降。
解决方案二:用 Collections.synchronizedList(new ArrayList<String>()) 代替new ArrayList<String>()。
public class CollectionTest { public static void main(String[] args) { // final List<String> list = new ArrayList<>(); // final List<String> list = new Vector<>(); final List<String> list = Collections.synchronizedList(new ArrayList<String>()); for (int i = 0; i <20; i++) { new Thread(new Runnable() { @Override public void run() { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } }解决方案三:用CopyOnWriteArrayList(写时复制)代替ArrayList。
public class CollectionTest { public static void main(String[] args) { //final List<String> list = new ArrayList<>(); //final List<String> list = new Vector<>(); //final List<String> list = Collections.synchronizedList(new ArrayList<String>()); final List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i <20; i++) { new Thread(new Runnable() { @Override public void run() { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); } }).start(); } } }这样做的好处是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会添加任何元素,这是一种读写分离的思想,读和写分别在不同的容器完成。
另外,像其他的数据结构:HashMap、HashSet,其底层都是不安全的,所能引起的异常和ArrayList一样,都是
java.util.ConcurrentModificationExecption,并且都有其相应的解决方法:ConcurrentHashMap、CopyOnWriteArraySet
CopyOnWriteArraySet 搞笑源码
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() { al = new CopyOnWriteArrayList<E>(); } }发现其底层还是个 CopyOnWriteArrayList ... ...换汤不换药... ...
========================================================================
附加知识:HashSet底层是HashMap,证据如下源码
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); /** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); } }HashMap添加元素是map.put(key,value);而HashSet添加元素是set.add(Element);这两个完全不一样,怎么能联系到一起
话不多说,还是看源码
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public boolean add(E e) { return map.put(e, PRESENT)==null; } }什么鬼!?HashSet的add方法里居然是HashMap的put方法,元素e添加到了key的位置,value的位置是个常量类型的Object,
名字叫PRESENT——礼物 ... ...