本文共 2589 字,大约阅读时间需要 8 分钟。
在多线程中,如果要并发的修改一个数据结构,那么很有可能会破坏这个数据结构。例如,一个线程可能要向一个散列表中插入一个元素,假如在调整各个桶之间的链接关系时被剥夺了控制权,而此时正好有另外一个线程正在遍历链表,则可能会产生异常或者死循环。
可以通过锁来保护共享的数据结构,但是选择线程安全的实现作为替代可能更容易一些。
任何集合类都可以通过使用同步包装器变成线程安全的:
ListsynchArrayList = Collections.synchronizedList(new ArrayList ());Map synchMap = Collections.synchronizedList(new HasMap ());
结果集合的方法使用锁加以保护,提供线程安全的访问。
如果在另一个线程可能进行修改时要对集合进行迭代,任然需要使用封锁。
synchronized(synchHashMap){ Iteratoriter = synchHashMap.keySet().iterator(); while(iter.hasNext()) //遍历}
如果使用for each 循环必须使用同样的代码,因为循环使用了迭代器。如果在迭代的过程中另一个线程修改集合,迭代器会失效,抛出ConcurrentModificationException异常,因此并发的修改可以被可靠的检测出来。
java.util.concurrent包提供了映像、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentLinkedQueue。这些集合通过复杂的算法,通过允许并发的访问数据结构的不同部分来使竞争极小化。
这些集合返回弱一致性的迭代器。这意味着迭代器不一定能反映出他们被构造之后的所有的修改,但是,他们不会将同一个值返回两次,也不会抛出ConcurrentModificationException的异常。
例如下面的例子:
import java.util.*;import java.util.concurrent.*;/* * ConcurrentLinkedQueue是“线程安全”的队列,而LinkedList是非线程安全的。 * * 下面是“多个线程同时操作并且遍历queue”的示例 * (01) 当queue是ConcurrentLinkedQueue对象时,程序能正常运行。 * (02) 当queue是LinkedList对象时,程序会产生ConcurrentModificationException异常。 * * @author skywang */public class Main { // TODO: queue是LinkedList对象时,程序会出错。 //private static Queuequeue = new LinkedList (); private static Queue queue = new ConcurrentLinkedQueue (); public static void main(String[] args) { // 同时启动两个线程对queue进行操作! new MyThread("ta").start(); new MyThread("tb").start(); } private static void printAll() { String value; Iterator iter = queue.iterator(); while(iter.hasNext()) { value = (String)iter.next(); System.out.print(value+", "); } System.out.println(); } private static class MyThread extends Thread { MyThread(String name) { super(name); } @Override public void run() { int i = 0; while (i++ < 6) { // “线程名” + "-" + "序号" String val = Thread.currentThread().getName()+i; queue.add(val); // 通过“Iterator”遍历queue。 printAll(); } } }}
使用ConcurrentLinkedQueue时不会报错
使用LinkedList时产生错误: