同步容器:
同步容器是将所有的方法都加了synchronized修饰,每个方法都要同步来执行,虽然保证了线程安全,但是因为线程获得的锁是整个对象的锁,所以降低了性能。
比如说Vector这个容器,Vector就是加了synchronized修饰的ArrayList,ArrayList和LinkedList都不是线程安全的,如果要用线程安全的list,可以使用Vector,不过更好的方法是使用Collections.synchronizedList()方法增强一下,这个方法是使用了装饰器模式(将已有的对象传入另一个类的构造器中创建新的对象来增强实现。)
还有Stack同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。
还有HashTable,它实现了Map接口,和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。HashTable是将一整个map都加了锁,降低了访问性能。
同步容器用起来也不全都是线程安全的,比如上篇博客说的添加和删除操作。
还有一个隐患是在使用迭代器遍历时发生的。如果在遍历过程中,自身线程或其他线程有操作对容器进行了修改,那么容器会抛出ConcurrentModificationException异常。
public class IteratorTest {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Iterator<Integer> iterator = vector.iterator();
while (iterator.hasNext()){
Integer integer = iterator.next();
System.out.println(integer);
if (integer == 5){
vector.remove(integer);//对容器进行删除修改
}
}
}
}
执行结果:
异常发生在checkForComodification方法中,这个方法的作用
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount 是记录容器被修改的次数,添加一个元素会+1,删除一个元素也会+1.
expectedModCount是容器期望的被修改次数,初始值modCount,在遍历的时候,expectedModCount就已经确定了,此时在对容器进行操作,modCount会+1,和expectedModCount不相等了,所以抛出了异常。
解决方法在单线程情况下可以将vector.remove(integer);替换成iterator.remove(integer);但仅仅适用于单线程,多线程还是会抛异常。
并发容器:
并发容器是将同步容器中的锁的粒度降低,保证了线程安全的同时提高了并发性能。而且还定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。
比如ConcurrentHashmap,ConcurrentHashmap中使用了分离锁机制。它做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组,ConcurrentHashMap的内部结构:
每个segment在写的时候都会有锁保护,所以最好的情况下是所有的segment都能进行多个线程并发的写。
还有CopyOnWriteArrayList,它是同步list的替代品,避免了在迭代期间对容器的加锁和复制。CopyOnWrite意思就是“写入时复制”,每次要对容器进行修改时,都创建一个容器的拷贝来进行修改。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。不过如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。
还有CopyOnWriteArraySet,与CopyOnWriteArrayList类似。