文档章节

ArrayList线程不安全分析

守望者-圣堂刺客
 守望者-圣堂刺客
发布于 2016/04/24 12:51
字数 1151
阅读 460
收藏 0

        之前一直听说ArrayList线程不安全的说法,百度了一下不安全的分析和列子,感觉都不太满意,最后边实在不行只好自己分析了。

1、多线程插入测试

        代码实例ArrayListTestDemo1.java:

package com.hjr.back16.common.util;

import static java.lang.System.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 测试ArrayList线程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo1 {
	public static Counter counter = new Counter();
	
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
//		for (int i = 0; i < 10000; i++) {
//			list.add(i);
//		}
		
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++) {
						list.add(1000*j + j); // 结果中抛出异常的代码
						counter.increment();
					}
				}
			}.start();
		}
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		out.println("list size: " + list.size());
		out.println("Counter: " + counter.getValue());
	}
}

class Counter {
	
	private int value;

	public synchronized int getValue() {
		return value;
	}

	public synchronized int increment() {
		return ++value;
	}

	public synchronized int decrement() {
		return --value;
	}
}
        Demo执行结果:
Exception in thread "Thread-5" Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 51
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 54
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 50
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 52
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
list size: 4858
Counter: 5050
        分析执行结果的时候,我们可能会发现两个问题:1)为什么抛出异常;2)为什么list size为什么比Counter小?

        1)为什么抛出异常

        我们先看下ArrayList.add()方法的源码:

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
        上边源码中,ensureCapacityInternal方法主要是判断需要的容量(size+1)是否大于 ArrayList的容量(elementData.length),如果大于 根据规则增加ArrayList的容量,否则 直接返回

        由于ArrayList.add()没有同步锁,所以多线程调用这个方法的时候,有可能多个线程拿到相同的size值去与ArrayList的容量做比较,而执行到elemntData[size++] = e;时却是有序的,这时,由于ensureCapacityInternal()没有适当的扩大ArrayList的容量,从而导致插入数据的长度大于ArrayList的剩余容量,于是也就抛出了越界的异常(java.lang.ArrayIndexOutOfBoundsException),图解:


        2)为什么list size为什么比Counter小

        对于这个问题,需要说明的是如代码所示,Counter.increment()方法是加了同步锁的,所以Counter输出的值就是list.add()成功的次数。然而,list size比Counter小,同样是因为多线程进入add方法时,拿到相同的size执行elementData[size++] = e;这句代码,而Java的自增操作又不是原子性操作,这样就导致了多个线程拿相同的size执行加1的操作,最后边就出现了list size小于Counter,也就小于世界成功执行add()方法次数的问题。

2、迭代器遍历List时做修改

        代码实例ArrayListTestDemo2.java:

package com.hjr.back16.common.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 测试ArrayList线程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo2 {
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < 10000; i++) {
			list.add(i);
		}
		
		// 列表遍历线程
		new Thread() {
			public void run() {
				Iterator<Integer> iterator = list.iterator();
				while(iterator.hasNext()) {
					iterator.next(); // 异常抛出的地方
				}
			}
		}.start();
		
		// 列表新增元素线程
		new Thread() {
			public void run() {
				for (int j = 0; j < 1000; j++) {
					list.add(1000*j + j);
				}
			}
		}.start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
        Demo执行结果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
	at java.util.ArrayList$Itr.next(ArrayList.java:831)
	at com.hjr.back16.common.util.ArrayListTestDemo2$1.run(ArrayListTestDemo2.java:25)
        异常来源代码片段ArrayList内部类Itr:
/**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        ... ...

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
        根据异常信息,我们知道异常是Itr.checkForComodification()这个方法抛出的 。其中,modCount是一个ArrayList用于记录ArrayList修改(包括列表元素的增删改等)次数的成员变量;expectedModCount是初始话一个新的Iterator时的modCount值。整个方法主要用于判断ArrayList在Iterator遍历的过程中,是否发生修改,如果发生修改expectedModCount与modCount不同),抛出异常(这其实是Java集合的一种错误机制:fail-fast)。


© 著作权归作者所有

共有 人打赏支持
守望者-圣堂刺客
粉丝 11
博文 23
码字总数 14730
作品 0
成都
私信 提问
加载中

评论(2)

守望者-圣堂刺客
守望者-圣堂刺客

引用来自“Evils0rr0w”的评论

请看楼主画的图,不懂为何Thread1的size+1后,Thread2跟Thread3的size能够知道变了,size又并不是volatilea
首先,对一个数据进行计算的流程我的理解分下边几个步骤:
1、把内存中的数据copy一份到CPU高速缓存
2、CPU使用高速缓存中的临时数据进行计算
3、运算后的结果暂时存在CPU高速缓存
4、在合适的时候(注意是"合适")计算结果同步到内存中;
三个线程都使用的是同一个List对象,Thread1的size和其它Thread的size是同一个对象,也就是同一个内存数据,所以Thread1中list的size改变了,其它的Thread肯定也是知道,虽然并不是每次修改的流程都和图中画的那样,其它Thread对应缓存中都是最新的size值,但是它确实会存在出现这种情况的概率,而且经测试概率还不低;
其次,对volatile的理解,volatile修饰的变量只是强制变量修改后立刻(注意“立刻”)同步到内存,同时使其它线程对应的缓存行失效(就算没有volatile进行修饰,CPU高速缓存里的数据也是可以失效的),这个并不是说如果没有volatile进行修饰,变量修改后就不同步到对应的内存变量了,所以,volatile对size的变化是否在多个线程中同步并没有决定性的意义,只能是尽量(这里是“尽量”)保证多线程进行数据计算的时候使用的是最新的值而已。
genericyzh
genericyzh
请看楼主画的图,不懂为何Thread1的size+1后,Thread2跟Thread3的size能够知道变了,size又并不是volatilea
Vector, ArrayList, LinkedList分析

Vector分析: 主要特性:有序,可随机访问,可变长,可重复,线程安全等。 其结构图: 其基本属性有: //存放元素的数组protected Object[] elementData;//元素个数protected int elementCoun...

ihaolin
2014/03/10
0
6
java中List接口的实现类 ArrayList,LinkedList,Vector 的区别 list实现类源码分析

java面试中经常被问到list常用的类以及内部实现机制,平时开发也经常用到list集合类,因此做一个源码级别的分析和比较之间的差异。 首先看一下List接口的的继承关系: list接口继承Collectio...

分享达人
2016/03/13
0
0
并发编程(二):非线程安全集合类

前言 线程不安全的集合类 ArrayList: 结果一: 结果二: 抛出异常:ArrayIndexOutofBoundsException异常; 现象:出现null值; 出现输出不全的现象; 抛出异常; 原因: ArrayList中的add方...

mengdonghui123456
2017/08/14
0
0
Collection源码分析(七):Vector源码分析

点开Vector的源码 可以看到 这个类继承自AbstractList 实现了List<E>, RandomAccess, Cloneable, java.io.Serializable三个接口,是不是感觉和ArrayList很像 注意到 Vector主要的三个变量 el...

Twelve_ZX
05/28
0
0
[Java 并发编程实战] 设计线程安全的类的三个方式(含代码)

发奋忘食,乐以忘优,不知老之将至。———《论语》 前面几篇已经介绍了关于线程安全和同步的相关知识,那么有了这些概念,我们就可以开始着手设计线程安全的类。本文将介绍构建线程安全类的...

seaicelin
05/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

RestClientUtil和ConfigRestClientUtil区别说明

RestClientUtil directly executes the DSL defined in the code. ConfigRestClientUtil gets the DSL defined in the configuration file by the DSL name and executes it. RestClientUtil......

bboss
今天
11
0

中国龙-扬科
昨天
2
0
Linux系统设置全局的默认网络代理

更改全局配置文件/etc/profile all_proxy="all_proxy=socks://rahowviahva.ml:80/"ftp_proxy="ftp_proxy=http://rahowviahva.ml:80/"http_proxy="http_proxy=http://rahowviahva.ml:80/"......

临江仙卜算子
昨天
9
0
java框架学习日志-6(bean作用域和自动装配)

本章补充bean的作用域和自动装配 bean作用域 之前提到可以用scope来设置单例模式 <bean id="type" class="cn.dota2.tpye.Type" scope="singleton"></bean> 除此之外还有几种用法 singleton:......

白话
昨天
8
0
在PC上测试移动端网站和模拟手机浏览器的5大方法

总结很全面,保存下来以备不时之需。原文地址:https://www.cnblogs.com/coolfeng/p/4708942.html

kitty1116
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部