前言
在观看多线程书籍的时候,经常会看到大家提及ArrayList与HashMap时候.皆会说明, ArrayList与HashMap类型都不是线程安全的. 那么,在传统的集合包内的集合类到底为什么线程非安全呢?在新的JUC包类又有什么可以替代呢? 由于篇幅问题, 我们将这个问题分成两章进行分析.本章中, 我们主要分析下ArrayList的部分.
本章主要包括如下几个部分:
- 为什么ArrayList是线程非安全的?
- 替代措施(Vector类 / Colletions封装 / JUC类)
ArrayList 与 非线程安全
ArrayList线程不安全详解
我们先执行如下例子:
import java.util.ArrayList; import java.util.List; / * ArrayList的非线程安全演示. * * */ 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
在输出时,我们会遇到这样的几种情况:
- 输出值为null;
- 数组越界异常;
- 某些线程没有输出值;
1. 输出值为null; //null //Thread-958 //Thread-958 //Thread-958 //Thread-959 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的源码
public boolean add(E e) { // 确保ArrayList的长度足够 ensureCapacityInternal(size + 1); // Increments modCount!! // ArrayList加入 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++; // 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); }
在上述过程中,会出问题的部分在于: 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的情况.
- 线程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关键字进行了加锁处理.
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(). 这边就是创建了一个临时空间用于辅助独占的处理.
# 转化方法如下 public static
List
synchronizedList(List
list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } # SynchronizedList类如下 static class SynchronizedList
extends SynchronizedCollection
implements List
{ // 其中的add方法如下 public void add(int index, E element) { synchronized (mutex) {list.add(index, element);} } }
- 使用JUC中的
CopyOnWriteArrayList类进行替换.(具体见JUC的集合框架篇)
后记
Reference
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208404.html原文链接:https://javaforall.net
