1.同步容器类
同步容器类包括Vector和Hashtable,或者通过Collections.synchronizedXxx等工长方法创建的。
这些实现线程安全的方式:将他们的状态封装起来,并对每个共有方法加锁。
1.1 同步容器类的问题
复合操作:迭代(反复访问元素,直到遍历完容器中所有元素)、条件运算(若没有则添加)。在其他线程并发地修改容器时,它们可能会变现出意料之外的行为。
如:
public static Object getLast(Vector list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public static void deleteLast(Vector list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
当两个线程同时分别执行上面的两个方法时。会出现问题。如线程A调用getLast(), 线程B调用deleteLast(); 调用交替执行的图如下:
结构是:getLast将抛出ArrayIndexOutOfBoundsException异常。
1.2 客户端加锁实现同步容器问题
我们解决的思路是通过加锁来保证Vector的大小在调用size和get之间不会发生变化。
如:
public static Object getLast(Vector list) {
synchronized(list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast(Vector list) {
synchronized(list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
1.3 迭代器与ConcurrentModificationException
同步容器类的迭代器在迭代时,如果被修改时,会变现出“及时失败”的异常,即ConcurrentModificationException异常。
除了显示的调用迭代会变现出“及时失败” 的异常外。隐式的调用以下方法也有可能出现“及时失败”,如:hashCode 、equals、containsAll、removeAll和retainAll等方法。
这种“及时失败”的迭代器并不是一种完备的处理机制,而只是“善意地” 捕获并发错误,因此只能作为并发问题的预警指示器。
解决办法:
- 对容器加锁
- 克隆容器
2. 并发容器
并发容器是从Java 5.0 后引入的。主要的目的是:通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。