设为首页添加收藏

您好! 欢迎来到上海金冠建材有限公司

微信
扫码关注官方微信
电话:021-56874279

您的位置:主页 > 新闻资讯 > 行业动态 >
行业动态

面试官:你工作3年了,ArrayList是线程不安全都没掌握,不应该啊

发布日期:2020-05-15 来源: 互联网


作为一名java程序员,对ArrayList,相信再熟悉不过了。这个类我们平时接触得最多的一个列表集合类。

小爱最近又去面试了,最近到某知名互联网公司面试,做了笔试题后,面试官刚好问ArrayList是线程安全还是非线程安全?

线程安全:指当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染等意外情况。

线程不安全:就是不提供数据访问时的数据保护,多个线程能够同时操作某个数据,从而出现数据不一致或者数据污染等意外情况。

public static void main(String[] args){ListString list = new ArrayList(); for (int i = 0; i i++) { new Thread(()-{ list.add(UUID.randomUUID().toString().substring(0, 6)); System.out.print(list); System.out.print(n },String.valueOf(i)).start(); }}

咋一看,程序没什么问题,运行有时候也是正常的,但运行几次就会抛出ConcurrentModificationException这样的异常。

public boolean add(E e) { /* 添加一个元素时,主要做了两步操作 1.判断列表的capacity容量是否足够,是否需要扩容 2.将元素放在列表的元素数组里面 */ ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }

private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }

private void ensureExplicitCapacity(int minCapacity) {modCount++; // overflow-conscious code if (minCapacity - elementData.length 0) grow(minCapacity); }

private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity 1); if (newCapacity - minCapacity 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }

从源码我们可以知道,但size + 1的这个需求长度大于了elementData这个数组的长度,那么这个数组就要进行扩容。

通过分析可以得出,ArrayList在多线程环境下是不安全的,那么问题来了,ArrayList不安全为什么要使用

CopyOnWriteArrayList的add操作都是在锁的保护下进行的。 这样做是为了避免在多线程并发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(); }}

从源码中我们可以得知写操作是在新的数组进行的,这时候如果有线程并发地写,则通过锁来控制,如果有线程并发地读取数据,则要具体问题具体分析:

如果写操作未完成,那么直接读取原数组的数据; 如果写操作已完成,但是引用还未指向新数组,那么读取的还是原数组的数据; 如果写操作已完成,且引用已指向了新数组,那么读取的是新数组的数据。

由于写操作需要拷贝数组,会消耗内存,在原数组的内容较多的情况下,可能导致年轻代(Young Generation)或者年老代(Old Generation)对于实时读体验一般,像拷贝数组、新增元素都需要一定的时间,可能在调用一个set操作后,读取到数据还是旧的,虽然CopyOnWriteArrayList 能保证数据最终的一致性,不能保证数据的实时一致性。

CopyOnWriteArrayList有一个很好的思想,那就是读写分离,读和写分开。这种思想还是挺值得我们借鉴的。

对于上面的程序,我们也可以用synchronized ,synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块。

public static void main(String[] args){ ListString list = new ArrayList(); for (int i = 0; i i++) { new Thread(()-{ synchronized (ArrayListDemo.class) { list.add(UUID.randomUUID().toString().substring(0, 6)); System.out.print(list); System.out.print(n } },String.valueOf(i)).start(); }}

关于ArrayList线程不安全的知识点本节就先简单分析到这。由于笔者水平有限,文中错漏缺点在所难免,希望读者批评指正。


分享到

新浪微博

分享到

朋友圈

分享到

QQ空间