Software Construction Series (5)

在编写多线程的时候遇到这样一个问题,在多个线程中往同一个集合中添加元素,最后在统一遍历进行处理。然而在实际运行中可有能会出现报错:NullPointerException || IndexOutOfBoundsException。然而在添加元素和遍历元素的过程中理应是不可能出现这样的问题的。


但是考虑到多线程,其实可能出现这样一种情况:

List<Integer> l = new ArrayList<>();

public void add(int x) {
  assert l.size() == 0;
  l.add(x);
}

add方法是可以在多线程中正常运行,并且不报错的,即在每个线程执行到add方法时,l.size() == 0。

这样就会在集合同一个位置多次添加,面对更复杂的数据结构,是会出现bug的。




既然知道了原因,就要有相应的解决办法:

JDK提供了对于线程安全的集合类,在JDK1.5之前多线程方面的处理比较简单,只有thread和synchronized两种机制。其中线程安全的集合类是:

List l = Collections.synchronizedList(new ArrayList<>());
Set s=Collections.synchronizedSet(new HashSet());
Map m=Collections.synchronizedMap(new HashMap());
Collection c=Collections.synchronizedCollection(new ArrayList());

可以看到是通过Decorator模式修饰原始集合类,其中原理与关键字synchronized的原理相同,即对这个对象加锁,从而使得只有一个线程能同时使用这个集合。

然而,这一方法的一个显然缺点是:大大降低了多线程的执行效率,在执行这个集合相关的操作时,多线程退化为串行执行。



在JDK1.5中有了更好的解决方案,推出了java.util.concurrent工具包以简化并发。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。

在JDK1.5中,线程安全的集合类:

import java.util.concurrent;

Collection<> c = new ConcurrentCollection<>();

其中实现线程安全的原理是使用的更加先进的像锁剥离技术。比如在ConcurrentHashMap中会把Map划分为几个片段,只对相关的几个片段上锁,同时允许多线程访问其他未上锁的片段,详细自行查阅JDK技术文档。

猜你喜欢

转载自www.cnblogs.com/KarlZhang/p/9159876.html