点开 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;
}
// 这里不是一个原子操作,是分两步执行,赋值和运算
elementData[size++] = e;
/**
* 多线程并发的时候,假如A、B两个线程,A挂起的时候,B线程拿到的size值和A是一样的,就会覆盖线程
* A的值,导致A的值为空。再者,因为size不能保证原子性,ArrayList 有默认数组大小,就会抛出数组下
* 标越界的异常
*
代码案例
public class ThreadRun implements Runnable{
private static List<String> listName = new ArrayList<String>();
@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());
}
}
执行结果
使用Collections.synchronizedList(new ArrayList<String>())保证线程安全
private static List<String> sycnListName =
Collections.synchronizedList(new ArrayList<String>());
@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原子类的资料