java中同步容器主要分为2类:
- Vector,Stack(其实是继承Vector,底层是数组实现的),HashTable
- Collections类中提供的静态工厂方法创建的类
1.为什么Vector是线程安全的?
源码所有的方法都加了synchronized。所以在多线程场景下可以单独使用。但是无法在复合操作的时候保证线程安全,需要主动添加锁。
public void delete( Vector v ) {
int lastIndex = v.size() - 1;
v.remove(lastIndex );
}
// 当多线程环境下调用会导致下标越界异常。所以需要主动添加锁
public void delete( Vector v ) {
synchronized(v) {
int lastIndex = v.size() - 1;
v.remove(lastIndex );
}
}
2.同步容器的问题
- 只保证了单个操作的现场安全性,无法保证复合操作的现场安全性,遇到这种情况,需要主动加锁
- 同步容器由于所有的方法都添加了锁,所以在多现场访问同一个容器的时候,只能顺序的进行执行,即排队。如get、add方法都需要排队,大大的降低了容器的并发能力
3.如何解决同步容器的问题,并发容器
JUC下有很多并发容器:可以提供大量支持高并发的访问的集合类。
- ConcurrentHahshMap中增加了对常用复合操作的方法,如putfAbsent(),replace()。这两个方法都是原子性的,可保证现场安全。
- CopyOnWriteArrayList和CopyOnWriteArraySet是Copy-On-Write的两种实现类。
- 改写方法是加锁的,读没有加锁;这样的好处是可以进行并发的读,当然读到的数据不一定是最新的。因为写时复制的思想是通过延迟更新的策略保证最终数据的一致性,并非强一致性。
Copy-On-Write:写时复制的容器,当我们向一个容器里面添加元素的时候,不直接添加,先把原容器进行copy,复制出一个新容器,然后在新容器里面添加元素,再把原容器的引用指向新容器中。
4.总结
- 同步容器是通过加锁保证单独操作的线程安全性,再复合操作中无法保证线程安全,需要手动加锁。因为同步容器内所有的方法都加锁,所以读写会阻塞。
- 并发场景中,使用JUC中自带的并发容器,但并不是所有的复合操作都能保证安全,可以使用并发容器自带的方法或自己加锁。