1、多线程异常案例
ArrayList底层是维护了一个数组。
ArrayList线程不安全,因为其add、remove等方法没有synchronized关键字修饰,也没有任何同步加锁处理。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!扩容一
elementData[size++] = e;//add方法默认放在最后一个
return true;
}
番外:注意现在的空参初始化ArrayList初始容量不是10了,是0,在第一次add的时候会将扩容为10。具体的ArrayList扩容机制可以参考大佬文章,作者:烟雨星空
https://blog.csdn.net/qq_26542493/article/details/88873168
ArrayList线程不安全的案例
public class UnsafeJH {
public static void main(String[] args){
//ArrayList线程不安全
List<String> list = new ArrayList<>();
for(int i = 1;i<=30;i++){//30个线程执行add操作
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
运行结果:
[null, f93845ca, 4011b4a3, 34fefde8, d2c0c25d]
Exception in thread "4" Exception in thread "10" Exception in thread "20" Exception in thread "27" Exception in thread "29" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at UnsafeJH.lambda$main$0(UnsafeJH.java:20)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
java.util.ConcurrentModificationException异常(并发修改异常)
2、解决方案
2.1、Vector
//add方法
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
//get方法
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
Vector中的大多数方法都用synchronized关键字修饰,可以解决线程安全问题,但是因为同意时间只能有一个线程访问资源,所以效率会很慢。
2.2、Collections.synchronizedList(list)
通过Collections工具类将一个线程不安全的ArrayList封装成为一个线程安全的。
Collections.synchronizedList(List list) 返回的是一个 SynchronizedList 的对象,这个对象以组合的方式将对 List 的接口方法操作,委托给传入的 list 对象,并且对所有的接口方法对象加锁,得到并发安全性。底层原理可以参考https://blog.csdn.net/weixin_38575051/article/details/94000044作者ToSimpleL
List<String> list = Collections.synchronizedList(new ArrayList<>());
2.3、写时复制 CopyOnWriteArrayList
读写分离的思想,当写操作的时候将,不直接往容器Object[]添加,而是先将当前容器Object[]进行Copy,生成一个新的Object[] newElements ;先往newElements 中添加要加的元素,再将原容器的引用指向新的容器;这样做的好处是可以对CopyOnWriteArrayList容器进行并发的读操作,而不需要加锁。从而提高效率。
List<String> copyOnWriteArrayList = new 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);//复制list,扩容长度+1
newElements[len] = e;//给复制后的list加元素
setArray(newElements);//写回list
return true;
} finally {
lock.unlock();//释放锁
}
}
get()源码不用加锁
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}