java本身对多线程的支持是很充分的,但在jdk1.5之前集合对多线程的操作几乎都是不安全的,但是集合又是我们经常用到的,如果用不好经常会出问题,下面我们来解析一下:就拿我们最简单的Vector类来说,我们先来看一下使用它的常规用法:
private static void removeObject(Vector<String> vector,int index) {
vector.remove(index);
}
这样的使用在多线程的情况下感觉会出现,index可能会超出vector容量的大小,可能你会说我们可以在使用之前先检测。
因此代码这样改成这样:
private void removeObject(Vector<String> vector,int index) {
if(vector.size()<index) {
return;
}
vector.remove(index);
}
确实可能是这样避免刚才出现的问题,如果我们还要用另外的一个方法,删除vector最后一个元素,因此我们添加了下面方法:
private void removeLast(Vector<String> vector) {
int index = vector.size() - 1;
vector.remove(index);
}
这样也是似乎也是没有问题了,如果两个线程是顺序执行的话,但实际情况我们可能并不能保证线程的顺序执行。因为我们想到了加锁来保证线程的安全,因此上面的方法就变成了,下面的代码:
private void removeObject(Vector<String> vector,int index) {
synchronized (vector) {
if(vector.size()<index) {
return;
}
vector.remove(index);
}
}
private void removeLast(Vector<String> vector) {
synchronized (vector) {
int index = vector.size() - 1;
vector.remove(index);
}
此时的代码确实是线程安全的,没有任何问题,如果我们现在想添加向集合里面添加元素,因此代码可以这样写:
private void addObject(Vector<String> vector, String e) {
vector.add(e);
}
当我们很高兴的时候,却出现了另外一个错误;ConcurrentModificationException,因此我们又不得不对上面的代码进行加锁,几乎我们对集合的任何操作都需要加锁,每个操作都加锁资源消耗是非常大的,而且特别是当我们对集合进行遍历操作的时候,如果其他线程因为加锁的原因必须等待,这样是很不好的。
但是到了jdk 5.0之后java提供了很多并发容器来改进同步容器的性能。比如:ConcurrentHashMap 和CopyOnWriteArrayList; 同时还新增了两种容器的类型:
Queue和BlockingQueue.
BlockingQueue是一种支持阻塞式添加和获取元素的方式,因此我们可以很简单的实现生产者和消费者模式。
针对上面的
ConcurrentModificationException异常我们可以CopyOnWriteArrayList集合可以很快搞定,这是一种写时才复制的容器,当我们在写的时候,发现有别的线程在读,我们先创建新的“副本”,从而可以实现可变性,不过如果我们每次在改变的时候都进行复现,这需要很大的开销。因此这种集合只能针对读操作比较对,写操作比较少的情况,似乎于观察者模式。
ConcurrentHashMap 改变了以往我们在每个操作上面都加锁的情况,采用新的加锁方式,我们可以叫做分段锁,这是一种基于散列的锁,任何读写操作都可以并发访问map,并且一定数量的写入线程可以并发执行。后面文章我们会一起学习。