一、简介
1.vector:JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。底层是数组结构,默认长度为10,每次扩容翻倍。Vector中的操作是线程安全的。因为大部分方法都使用synchronized修饰。
例如add方法:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
get方法:
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
2.CopyOnWriteArrayList:JDK 1.5引入,一个线程安全的List实现类,底层为数据,在写入时加锁后复制一个比原来长度多一的数据,然后将写入数据添加到末尾,读的时候无锁共享。
add方法:添加元素时加锁,复制时内存占用翻倍。
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();
}
}
get方法:不加锁
public E get(int index) {
return get(getArray(), index);
}
注意:如果在t1线程读的时候,t2线程在写入,就会造成t1读的数据是t2放入之前的数据,所以只能保证最终的数据一致性,并不能保证数据的实时性。
二、代码举例
说明:创建两个线程分别向CopyOnWriteArrayList、Vector、ArrayList容器对象添加10000个元素,最后打印容器长度。CopyOnWriteArrayList结果:20000,Vector结果:20000,ArrayList:<20000(添加元素方法不是同步的),或者数组越界异常(两个线程在需要扩容的时候,t1执行了扩容,t2判断长度够用,直接放入元素,但t1只扩容了自己的位置,1个位置两个线程都要放元素,一个放完,另一个就抛出异常)。
public class synList {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
Vector<Integer> vector = new Vector<>();
List<Integer> list = new ArrayList<>();
new Thread(() ->{
for (int i = 0; i < 10000; i++) {
copyOnWriteArrayList.add(new Integer(i));
}
}).start();
new Thread(() ->{
for (int i = 0; i < 10000; i++) {
copyOnWriteArrayList.add(new Integer(i));
}
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(copyOnWriteArrayList.size());
}
}
三、vector和CopyOnWriteArrayList对比
1.vector:锁粒度大,排它锁,性能较低,但保证数据一致性。
2.CopyOnWriteArrayList:内存占用高,只能保证最终的数据一致性,并不能保证数据的实时性,但在读多写少的场景中效率高。