这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
ArrayList是常用的一个集合类,底层基于数组实现,但是它在并发环境下线程不安全,以下是简单的一些分析。
ArrayList线程不安全
Demo
public class ArrayListNotSafeDemo {
static List<String> list =new ArrayList<>();
public static void main(String[] args) {
for (int i = 0; i <3 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}).start();
}
}
}
运行结果:
[null, 342a081b]
[null, 342a081b]
[null, 342a081b]
将i修改为10,加大并发的数量可以看到报异常:
Exception in thread "Thread-8" java.util.ConcurrentModificationException
这是并发修改异常
源码分析
//Appends the specified element to the end of this list.
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
可以看到add方法并没有加锁,所以在多个线程并发操作的时候会出现线程安全问题
解决方法
- 1.Vector
- 2.Collections.synchronizedList()
- 3.CopyOnWrite(写时复制)
Vector
class VectorDemo{
static Vector<String> vector=new Vector<>();
public static void main(String[] args) {
for (int i = 0; i <10 ; i++) {
new Thread(()->{
vector.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(vector);
}).start();
}
}
}
运行后发现并没有出现线程不安全的问题(没有出现并发修改异常),我们查看Vector类的源码发现它的方法都用synchronized修饰,所以Vector是线程安全的,但是性能比较低。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Collections.synchronizedList()
Collections.synchronizedList(list);
相当于Collections工具类提供了一个方法给原来没有加锁的集合加锁,当然类似的还有
Collections.synchronizedMap(), Collections.synchronizedSet()等等
写时复制(重点)
class CopyOnWriteDemo {
static List<String> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
来查看下CopyOnWriteArrayList中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();
}
}
- CopyOnWriteArrayList就是写时复制的容器,每次添加Objcet对象时不是直接向原Object[]数组中添加,而是复制一个长度为原数组长度+1的新数组,把要添加的数据写到新数组上。
- 写完之后在把原来的引用指向新数组setArray(newElements),最后再释放锁,让其他线程进行写操作。
- 这样做的好处是在进行读操作的时候不用加锁,保证了并发性,同时这也是读写分离思想的体现。