ArrayList是线程不安全的,因为在他的 add 方法添加元素时,为了保证并发性,没有加锁。
public class CollectionTest {
public static void main(String[] args) {
final List 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
public class CollectionTest {
public static void main(String[] args) {
// final List list = new ArrayList<>();
// final List list = new Vector<>();
final List list = Collections.synchronizedList(new ArrayList());
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 list = new ArrayList<>();
//final List list = new Vector<>();
//final List list = Collections.synchronizedList(new ArrayList());
final List 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();
}
}
}
CopyOnWriteArrayList源码分析
public class CopyOnWriteArrayList
implements List, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/**
* The lock protecting all mutators. (We have a mild preference
* for builtin monitors over ReentrantLock when either will do.)
*/
final transient Object lock = new Object();
/** The array, accessed only via getArray/setArray. */
// Android-changed: renamed array -> elements for backwards compatibility b/33916927
private transient volatile Object[] elements; //volatile修饰,保证其可见性
...
...
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
...
...
这样做的好处是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会添加任何元素,这是一种读写分离的思想,读和写分别在不同的容器完成。
另外,像其他的数据结构:HashMap、HashSet,其底层都是不安全的,所能引起的异常和ArrayList一样,都是
java.util.ConcurrentModificationExecption,并且都有其相应的解决方法:ConcurrentHashMap、CopyOnWriteArraySet
CopyOnWriteArraySet 搞笑源码
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() {
al = new CopyOnWriteArrayList();
}
}
发现其底层还是个 CopyOnWriteArrayList ... ...换汤不换药... ...
========================================================================
附加知识:HashSet底层是HashMap,证据如下源码
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap 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 HashMap 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 extends AbstractSet implements Set, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap 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——礼物 ... ...