ArrayList不允许写操作没执行完就执行读操作,正在读的时候不允许去写,必须写完再读,读完再写。
一:ArrayList不安全示例
使用ArrayList每次打印的集合数量可能会小于10000,而使用Vector每次都是10000
public class ListTest {
public static void main(String[] args) throws InterruptedException {
ThreadGroup group = new ThreadGroup("myGroup");
List list = new ArrayList<>();
// Vector list = new Vector<>();
MyRunnable runnable = new MyRunnable(list);
for (int i = 0; i < 10000; i++) {
new Thread(group, runnable).start();
}
// 保证所有线程都执行完毕
while (group.activeCount() > 0) {
Thread.sleep(10);
}
System.out.println(list.size());
}
}
class MyRunnable implements Runnable {
private List list;
public MyRunnable(List list) {
this.list = list;
}
@Override
public void run() {
try {
Thread.sleep((int)(Math.random() * 2));
} catch (InterruptedException e) { }
Random random = new Random();
list.add(Thread.currentThread().getName() + "\t" + random.nextInt(100));
}
}
ArrayList源码
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
transient Object[] elementData;
private int size;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// elementData[index] = element
// size++
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 先赋值
elementData[index] = element;
// 再更新数量
size++;
}
}
通过源码可以看到ArrayList是通过一个Object[]数组来实现的,ArrayList在添加一个元素的时候,至少需要两步操作:1. 更新集合大小 2. 将元素添加到集合中, 即 elementData[size++] = e; (注意是size++, 而不是++size, 先使用值,然后再相加) add方法并没做什么同步加锁的处理,假如有两个线程,刚开始size=0; Thread-A线程将元素e添加到集合中,但并没有完成自加的操作,此时size仍然为0,elementData[0] = e,然后CPU切换到Thread-1线程,添加e2元素,因Thread-0并没有完成自加操作所以此时size仍然为0,Thread-1执行完所有的add操作,结果elementData[0] = e2; size=1,Thread-1执行完CPU切换到Thread-0,完成自增赋值操作,因Thread-1已经完成自层操作了,所以当时的size=1,Thread-0完成自增后size=2了,可以看到size为2,但是两个线程都是对索引0的位置操作,最终集合的长度为1,状态不一致。
elementData[size++] = e 并不是原子操作,实际上至少会被分解为4个原子操作
- 读取size的值
- int newSize = size + 1;
- size = newSize;
- elementData[size] = e;
ArrayList是线程不安全的,可以通过Collections包装成线程安全的集合
List<String> list = Collections.synchronizedList(new ArrayList<String>());
Collections: 集合工具类
package java.util;
public class Collections {
// 将一些非线程安全的集合包装成线程安全的集合
public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> Set<T> synchronizedSet(Set<T> s);
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
public static <T> List<T> synchronizedList(List<T> list){
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);
// SynchronizedList
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
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;
}
}
// SynchronizedCollection
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
// 同步锁
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
// 同步锁为this,当前对象
mutex = this;
}
public boolean add(E e) {
// add 方法使用了同步锁: this
// 同一时间只能添加一个
synchronized (mutex) {return c.add(e);}
}
}
}
示例一
public class WriteThread extends Thread {
private final List<Integer> list;
public WriteThread(List<Integer> list) {
super("WriteThread");
this.list = list;
}
@Override
public void run() {
for (int i = 0; true; i++) {
list.add(i);
}
}
}
public class ReaderThread extends Thread {
private final List<Integer> list;
public ReaderThread(List<Integer> list) {
super("ReaderThread");
this.list = list;
}
@Override
public void run() {
while (true) {
for (int n : list) {
System.out.println(n);
}
}
}
}
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
new WriteThread(list).start();
new ReaderThread(list).start();
}
}
Exception in thread "ReaderThread" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.company.example.chapter02.example2.ReaderThread.run(ReaderThread.java:25)
示例2
public static void main(String[] args) {
// 包装成同步集合
final List<Integer> list = Collections.synchronizedList(new ArrayList<>());
// SynchronizedCollection#add 已经加了同步锁this,所以不会发生同步写
new WriteThread(list).start();
// 虽然写与写不会同步,但是目前写的同时可以读,这样是不行的,必须保证写的时候不能读,读的时候不能写
// 所以在读的时候也要加锁,而且读的时候加的锁必须和写的锁一致
// 如果能保证读与写互斥,写与写互斥就没有线程安全问题
// 注意:虽然Collections.synchronizedList是线程安全的,但是并不是说你的程序也安全,同时也要保证自己的逻辑也要安全
new ReaderThread(list).start();
}
public class ReaderThread extends Thread {
// ...
@Override
public void run() {
while (true) {
// 读写互斥
synchronized (list) {
for (int n : list) {
System.out.println(n);
}
}
}
}
}
示例三
CopyOnWriteArrayList是线程安全的,copy-on-write:写时复制,当对集合执行写操作是,内部已确保安全的数组就会被整体复制,复制之后,就无需在使用迭代器依次读取元素时担心元素被修改了,所以在读时也不会发生java.util.ConcurrentModificationException
public static void main(String[] args) {
// WriteThread 和 ReaderThread 和示例一完全一样
// 只需要使用CopyOnWriteArrayList
final List<Integer> list = new CopyOnWriteArrayList<>();
new WriteThread(list).start();
new ReaderThread(list).start();
}
CopyOnWriteArrayList
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 锁
final transient ReentrantLock lock = new ReentrantLock();
// 数组
private transient volatile Object[] array;
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;
// 将新数组整体复制给array
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
// get无需加锁
public E get(int index) {
return get(getArray(), index);
}
}
使用copy-on-write时,每次执行写操作都会执行复制。因此频繁执行写操作时,如果使用CopyOnWriteArrayList会比较花费时间,如果写操作较少读操作较多就非常适用该集合。
Vector
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
protected Object[] elementData;
protected int elementCount;
// 线程安全,使用synchronized
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
}
将上面的list声明为Vector可以看到每次执行集合的长度都是10000
三:Vector与ArrayList比较
Vector与ArrayList主要在于线程安全和性能上的区别。
- Vector与ArrayList本质上都是一个Object[] 数组,ArrayList提供了size属性,Vector提供了elementCount属性,他们的作用是记录集合内有效元素的个数。
- Vector是线程安全的集合类,ArrayList并不是线程安全的类。Vector类对集合的元素操作时都加了synchronized,保证线程安全。
- Vector与ArrayList的扩容并不一样,Vector默认扩容是增长一倍的容量,Arraylist是增长50%的容量。
- Vector与ArrayList的remove,add(index,obj)方法都会导致内部数组进行数据拷贝的操作,这样在大数据量时,可能会影响效率。