为什么说ArrayList是线程不安全的,使用并发集合类解决集合不安全问题

点开 ArrayList 的 add 方法的源码

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    // 这里不是一个原子操作,是分两步执行,赋值和运算
    elementData[size++] = e;
    /**
     * 多线程并发的时候,假如A、B两个线程,A挂起的时候,B线程拿到的size值和A是一样的,就会覆盖线程                
     * A的值,导致A的值为空。再者,因为size不能保证原子性,ArrayList 有默认数组大小,就会抛出数组下 
     * 标越界的异常 
     * 
      

代码案例

public class ThreadRun implements Runnable{
	
	private static List listName = new ArrayList();
	
	@Override
	public void run() {
		listName.add(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) throws Exception {
		ThreadRun ms = new ThreadRun();
		for (int i = 0; i < 15; i++) {
			new Thread(ms,"线程"+i).start();
		}
		// 此处让线程停留300毫秒,以便线程都执行完成后,size()方法获取的线程数量更加准确
		Thread.sleep(300);
		System.out.println("======>"+listName.size()+","+listName.toString());
	}
}

执行结果

为什么说ArrayList是线程不安全的,使用并发集合类解决集合不安全问题_第1张图片

使用Collections.synchronizedList(new ArrayList())保证线程安全

    private static List sycnListName = 
                              Collections.synchronizedList(new ArrayList());
	
	@Override
	public void run() {
		sycnListName.add(Thread.currentThread().getName());
	}

并发集合类

ConcurrentHashMap

util.concurrent 包中的 ConcurrentHashMap 类(也将出现在JDK 1.5中的 java.util.concurrent 包中)是对 Map 的线程安全的实现,比起 synchronizedMap 来,它提供了好得多的并发性。多个读操作几乎总可以并发地执行,同时进行的读和写操作通常也能并发地执行,而同时进行的写操作仍然可以不时地并发进行(相关的类也提供了类似的多个读线程的并发性,但是,只允许有一个活动的写线程) 。ConcurrentHashMap 被设计用来优化检索操作;实际上,成功的 get() 操作完成之后通常根本不会有锁着的资源。要在不使用锁的情况下取得线程安全性需要一定的技巧性,并且需要对Java内存模型(Java Memory Model)的细节有深入的理解。

CopyOnWriteArrayList

如果您正在使用一个普通的 ArrayList 来存放一个侦听器列表,那么只要该列表是可变的,而且可能要被多个线程访问,您 就必须要么在对其进行迭代操作期间,要么在迭代前进行的克隆操作期间,锁定整个列表,这两种做法的开销都很大。当对列表执行会引起列表发生变化的操作时, CopyOnWriteArrayList 并不是为列表创建一个全新的副本,它的迭代器肯定能够返回在迭代器被创建时列表的状态,而不会抛出 ConcurrentModificationException 。在对列表进行迭代之前不必克隆列表或者在迭代期间锁 定列表,因为迭代器所看到的列表的副本是不变的。换句话说, CopyOnWriteArrayList 含有对一个不可变数组的一个可变的引用,因此,只要保留好那个引用,您就可以获得不可变的线程安全性的好处,而且不用锁定列表。

Collections.synchronizedList

当synchronizedList传入的参数类型是ArrayList时, 因为ArrayList实现了RandomAccess接口,所以synchronizedList会构建一个SynchronizedRandomAccessList对象,不过没关系,SynchronizedList是SynchronizedRandomAccessList的父类,List 对象直接维护了传递进来的参数List类型参数,而在get set add remove等方法中的实现都用线程同步语句synchronized (mutex)封装起来。那么mutex这把锁是谁呢? 看起来要到super(list)里面去找了.于是就能找到SynchronizedList的父类SynchronizedCollection.

结束语

同步的集合类 Hashtable 和 Vector ,以及同步的包装器类 Collections.synchronizedMap 和Collections.synchronizedList ,为 Map 和 List 提供了基本的有条件的线程安全的实现。然而,某些因素使得它们并不适用于具有高度并发性的应用程序中――它们的 集合范围的单锁特性对于可伸缩性来说是一个障碍,而且,很多时候还必须在一段较长的时间内锁定一个集合,以防止出现 ConcurrentModificationException s异常。 ConcurrentHashMap 和CopyOnWriteArrayList 实现提供了更高的并发性,同时还保住了线程安全性,只不过在对其调用者的承诺上打了点折扣。ConcurrentHashMap 和 CopyOnWriteArrayList 并不是在您使用 HashMap 或 ArrayList 的任何地方都一定有用,但是它们是设计用来优化某些特定的公用解决方案的。许多并发应用程序将从对它们的使用中获得好处。

提示:关于并发编程,也可以查看Java原子类的资料

你可能感兴趣的:(Java文档)