ArrayList
当我们构造一个ArrayList的时候,会为我们创建一个初始容量为10的空列表。
/**
* Constructs an empty list with an initial capacity of ten.
*/
//构造一个初始容量为 10 的空列表
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以看到,elementData是object类型的数组,一开始没有元素时都是空的,当添加第一个元素后会展开为默认容量。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList线程不安全的案例
ArrayList为了保证并发性,其方法没有用synchronized修饰。
单线程情况没有问题
package listsafe;
import java.util.ArrayList;
import java.util.List;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.forEach(System.out::println);
}
}
多线程情况
package listsafe;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i=1; i <= 3 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
这些出现了数据丢失。
这次换成30个线程
package listsafe;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
出现ConcurrentModificationException(并发修改异常)
注意代码中的toString,正是由于ArrayList在添加过程中,执行的tostring方法会使用迭代器遍历list,在遍历过程中会有其他线程对其进行修改,导致modCount!=expectedCount引发异常。
解决方案
方法一:用Vector(不过不推荐)并发性下降。
package listsafe;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new Vector<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
方法二:Collections.synchronizedList(new ArrayList<>())
Collections是java集合工具类,可以用来处理集合问题。
package listsafe;
import java.util.*;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
方法三:CopyOnWriteArrayList(写时复制)
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
以下是CopyOnWriteArrayList进行添加操作的源码
大致的流程是:在进行添加操作之前,先利用ReentranLock进行加锁,也就是写的时候只允许一个线程写。然后将原数组进行扩容,扩容的长度是原数组的长度加1,之后将数据加入到新数组的末尾。
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();
}
}
package listsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
Set
不安全的案例和list一样。
package listsafe;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
public class SetNotSafeDemo {
public static void main(String[] args) {
Set<String> list = new HashSet<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
解决方法
方法一:Collections.synchronizedSet
package listsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class SetNotSafeDemo {
public static void main(String[] args) {
Set<String> list = Collections.synchronizedSet(new HashSet<>());
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
方法二:CopyOnWriteArraySet
可以看到,CopyOnWriteArraySet底层用的还是CopyOnWriteArrayList。
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
package listsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetNotSafeDemo {
public static void main(String[] args) {
Set<String> list = new CopyOnWriteArraySet<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
在这里多说一下。
HashSet的底层实现用的是HashMap,是将添加的元素作为HashMap的key,其value是一个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<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
Map
验证
package listsafe;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
public class MapNotSafeDemo {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
解决方法:ConcurrentHashMap
package listsafe;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class MapNotSafeDemo {
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for(int i=1; i <= 30 ; i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
或者是Collections.synchronizedMap
package listsafe;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class MapNotSafeDemo {
public static void main(String[] args) {
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
for(int i=1; i <= 30 ; i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}