并发编程(三):同步容器和并发容器

来源:互联网 发布:仁爱路99号碧格网络 编辑:程序博客网 时间:2024/06/05 11:19

前言

Java 中有些集合和非线程安全,而有些集合是线程安全,后者又被称为是Java中的同步容器,因为它能满足操作的原子性,保持数据同步。有些容器时Java自带的,而有些是通过Collections提供的方法包装的。

同步容器

在Java中,同步容器主要包括2类:
1)Vector、Stack、HashTable

Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。

Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。

HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。

2)Collections类中提供的静态工厂方法创建的类

Collections类是一个工具提供类。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。更重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,同步容器中的方法采用了synchronized进行了同步,缺点是必然会影响到程序的执行性能。

同步容器和并发容器的区别

同步容器类虽然都是线程安全的,但是在某些情况下(复合操作),仍然需要加锁来保护;

同步容器会导致多个线程中对容器方法调用的串行执行,降低并发性,因为它们都是以容器自身对象为锁,所以在需要支持并发的环境中,可以考虑使用并发容器来替代。

并发容器:并发容器是针对多个线程并发访问设计的,在jdk5.0引入了concurrent包,其中提供了很多并发容器,如ConcurrentHashMap,CopyOnWriteArrayList等。并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以在并发环境下实现更高的吞吐量。

另外并发容器提供了一些在使用同步容器时需要自己实现的复合操作,包括putIfAbsent等,但是由于并发容器不能通过加锁来独占访问,所以我们无法通过加锁来实现复合操作了。

复合操作

同步容器中的所有自带方法都是线程安全的,因为方法都使用synchronized关键字标注。但是,对这些集合类的复合操作无法保证其线程安全性。对于什么是复合操作一直很不理解?为甚线程安全的同步容器遇到符合操作后会变的线程不安全了?下面以线程安全的Vector做例说明。

像Vector这样的同步容器的所有共有方法全都是synchronized的。也就是说,我们可以在多线程场景中放心的使用单独这些方法,因为这些方法本身的确是线程安全的。那么为什么又说复合操作无法保证线程安全呢?这里举个栗子,我们定义如下删除Vector中最后一个元素方法:

public Object deleteLast(Vector v){    int lastIndex  = v.size()-1;    v.remove(lastIndex);}

上面这个方法是一个复合方法,包括size()和remove(),无论是size()方法还是remove()方法都是线程安全的,但是,如果多线程调用该方法的过程中有,remove方法有可能抛出ArrayIndexOutOfBoundsException。因为在多线程情况下,这块代码是没有锁住的,也就是说有可能出现并发,当A线程执行int lastIndex = v.size()-1;后,其他多个线程也同样执行,并且减少到0时,也就是说,当当前索引值不再有效的时候,将会抛出这个异常。

解决:为了避免复合操作带来的不安全,可以给这个复合操作加上锁来同步代码块;或者使用不同于同步容器的并发容器来解决这个问题;

并发容器

从Java5开始,Java.util.concurent包下,提供了大量支持高效并发的访问的集合类;这些线程分为两类:

1.以Concurrent开头的集合类:如ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentskipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque;

以Concurrent开头的集合类支持并发访问,写入线程的所有操作都是锁安全的,但是读取操作时没有锁安全的,在并发写入操作时性能良好;
ConcurrentLinkedQueue:不允许使用Null元素,多个线程访问时无需等待,具有高效访问的特效,适用于多个线程访问一个集合;
ConcurrentHashMap:支持16个线程的并发写入,当超过16时,多出的线程等待,此外可以通过concurrencyLevel参数设定线程数;

2.以CopyOnWrite开头的集合类:如CopyOnWriteArrayList、CopyOnWriteArraySet;

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。