首先,ArrayList不是线程安全的,多个线程中填工时操作一个ArrayList对象,则会出现不确定的结果。
本文主要包括如下几个部分:
为什么ArrayList是线程非安全的?
替代方案(Vector类 / Colletions封装 / JUC类)
ArrayList是线程非安全详解 先看如下示例,通过ArrayList的add方法来分析ArrayList的线程安全问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.ArrayList;import java.util.List;class UnsafeArrayListThread extends Thread { public void run () { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } UnsafeArrayList.arrayList.add(Thread.currentThread().getName()+" " +System.currentTimeMillis()); } } public class UnsafeArrayList { public static List arrayList = new ArrayList (); public static void main (String[] args) { Thread []threadArray = new Thread [1000 ]; for (int i = 0 ;i<threadArray.length;i++){ threadArray[i] = new UnsafeArrayListThread (); threadArray[i].start(); } for (int i = 0 ;i<threadArray.length;i++){ try { threadArray[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0 ;i<arrayList.size();i++){ System.out.println(arrayList.get(i)); } } }
在输出时,我们会遇到这样的几种情况:
输出值为null;
数组越界异常;
某些线程没有输出值;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 输出值为null; null Thread-11553158258958 Thread-51553158258958 Thread-71553158258958 Thread-61553158258959 3. 某些线程没有输出值; 某些输出缺少. 2. 数组越界异常; Exception in thread "Thread-874" java.lang.ArrayIndexOutOfBoundsException: 823 at java.util.ArrayList.add(ArrayList.java:441) at com.yanxml.multithreading.art.collection.UnsafeArrayListThread.run(UnsafeArrayList.java:17)
这些都是在多线程中使用ArrayList类所会遇到的问题.下面我们分别根据上面进行一一解答:
我们看下ArrayList的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public boolean add (E e) { ensureCapacityInternal(size + 1 ); elementData[size++] = e; return true ; } private void ensureCapacityInternal (int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); } private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
在上述过程中,会出问题的部分在于: 1. 增加元素 2. 扩充数组长度;
增加元素过程中较为容易出现问题的部分在于elementData[size++] = e;.赋值的过程可以分为两个步骤elementData[size] = e;size++;
我们分别使用两个线程来模拟插入过程.例如有两个线程,分别加入数字1与2。
运行的过程如下所示:
线程1 赋值 element[1] = 1; 随后因为时间片用完而中断;
线程2 赋值 element[1] = 2; 随后因为时间片用完中断;
此处导致了之前所说的一个问题(有的线程没有输出); 因为后续的线程将前面的线程的值覆盖了.
线程1 自增 size++; (size=2)
线程2 自增 size++; (size=3)
此处导致了某些值为null的问题.因为原size=1, 但是因为线程1与线程2都将值赋值给了element[1],导致了element[2]内没有值,被跳过了.指针index指向了3.所以,导致了某些情况下值为null的情况.
数组越界情况. 我们将上方的线程运行图更新下进行演示:
前提条件: 当前size=2 数组长度为2.
线程1 判断数组是否越界.因为size=2 长度为2,没有越界.将进行赋值操作.但是因为时间片问题导致了中断.
线程2 判断数组是否越界.因为size=2 长度为2,没有越界.将进行赋值操作.但是因为时间片问题导致了中断.
线程1 重新获取到主动权.上文判断了长度刚刚好够用.进行赋值操作element[size]=1,并且size++
线程2 因为上文判断了数组没有越界.所以进行赋值操作.但是此时的size=3了.再执行element[3]=2. 导致了数组越界了.
由此处可以看出因为数组的当前指向size并未进行加锁的操作,导致了数组越界的情况出现. 所以, ArrayList类是非线程安全的类!
解决措施 使用Vector类进行替换 将上文的public static List arrayList = new ArrayList();替换为public static List arrayList = new Vector<>(); 原理:使用了synchronized关键字进行了加锁处理。
1 2 3 4 5 6 public synchronized boolean add (E e) { modCount++; ensureCapacityHelper(elementCount + 1 ); elementData[elementCount++] = e; return true ; }
使用Collections.synchronizedList(List)进行替换 public static List arrayList = Collections.synchronizedList(new ArrayList()); 原理: 使用mutex对象进行维护处理.Object mutex = new Object(). 这边就是创建了一个临时空间用于辅助独占的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 转化方法如下 public static <T> List <T> synchronizedList (List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList <>(list) : new SynchronizedList <>(list)); } # SynchronizedList类如下 static class SynchronizedList <E> extends SynchronizedCollection <E> implements List <E> { public void add (int index, E element) { synchronized (mutex) {list.add(index, element);} } }
使用JUC中的CopyOnWriteArrayList类进行替换
后记 上集合类与线程安全性的比较图。