同步容器类存在的问题
同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作,在复合操作,如:迭代、跳转已经条件运算中,这些操作可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早起迭代器设计的时候并没有考虑并发修改的原因。
开门见山,我们直接来看两个例子:
public class UseVector {
/**
* 遍历向量
* @return
*/
public void travelVector(Vector<String> list) {
for (String str: list) {
System.out.println(str);
}
}
/**
* 该方法的主要作用是:从数组中移除指定的元素
* @param list
* @param target
* @return 抛出异常java.util.ConcurrentModificationException
*/
public Collection<String> removeOne(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
//抛出java.util.ConcurrentModificationException异常
for(String str : list) {
if(target.equals(str)) {
list.remove(str);
}
}
return list;
}
public static void main(String[] args) {
//定义了一个动态数组,并向动态数组中添加3个元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//遍历这个数组
demo.travelVector(vector);
//从这个动态数组中移除3这个元素
demo.removeOne(vector, "3");
}
}
该案例创建了一个动态数组Vector并向动态数组中添加3个元素,同时该类有两个方法,分别是travelVector()遍历动态数组方法和removeOne从动态数组中移除掉目标元素(target)的方法。运行上诉代码,程序抛出异常:
java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
at java.util.Vector$Itr.next(Vector.java:1163)
at com.springchang.threadcore.test.UseVector.removeOne(UseVector.java:31)
at com.springchang.threadcore.test.UseVector.main(UseVector.java:51)
果不其然,该代码抛出了在并发编程中常见的ConcurrentModificationException异常,并追踪上述代码得知是removeOne方法中的list.remove(str);这一行抛出的异常。咱暂时先不管为啥抛出的异常,先想想有没有其他代替方案解决我们的需求:遍历我们的数组,当数组元素等于目标值时将其移除。上述循环数组用的是增强for循环的方法,我们不妨换成另一种方式,用Iterator迭代器试一试。
public class UseVector {
/**
* 通过迭起器方法移除一个元素
* @param list
* @param target
* @return 抛出异常java.util.ConcurrentModificationException
*/
public Collection<String> removeOneByIt(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String str = it.next();
if(target.equals(str)) {
list.remove(str);
}
}
return list;
}
public static void main(String[] args) {
//定义了一个动态数组,并向动态数组中添加3个元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//从这个动态数组中移除3这个元素
demo.removeOneByIt(vector, "3");
}
}
悲伤的是Iterator迭代器并没有解决我们的问题,同样的Iterator也抛出了ConcurrentModificationException异常。在JDK 1.5之前有没有解决的方案呢?有的,请看以下代码:
public class UseVector {
/**
* 安全的从数组中移除元素的方法
* @param list
* @param target
* @return 不会抛出抛出异常java.util.ConcurrentModificationException
*/
public Collection<String> removeOneBySafe(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
//该方法是单现成的,所以安全
for(int i = 0; i < list.size(); i++) {
if(target.equals(list.get(i))) {
list.remove(target);
}
}
return list;
}
public static void main(String[] args) {
//定义了一个动态数组,并向动态数组中添加3个元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//从这个动态数组中移除3这个元素
demo.removeOneBySafe(vector, "3");
System.out.println("移除后的数组内容:" + vector);
}
}
如上所述,上述代码并没有使用增强的for循环来遍历数组,也不用Iterator迭代器来遍历数组。那么前两者与最后一个例子的区别在哪里呢?这是因为无论是增强的for循环还是迭代器在运行的时候迭代器多了一个线程来控制游标位置,控制游标当前的指向位置,而案例3是单线程的,只有一个i下标指向当前元素位置,读和写操作都在同一线程中完成的,故而安全。
同步类容器的使用
同步类容器如Vector,Hashtable等这些容器的同步功能都是有JDK的Collections.synchronized***等工厂方法去创建实现的。其底层机制用synchronized关键字对每个公用的方法都进行同步,或者使用Object mutex对象锁的机制使得每次只能有一个线程访问容器的状态。使用的代码如下:
public static void main(String[] args) {
//定义了一个动态数组,并向动态数组中添加3个元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
Collection<String> col = Collections.synchronizedCollection(vector); //先将线程不安全的同步类vectory转为线程安全的col
System.out.println(col);
}
要想把同步类变成线程安全,先使用Collections.synchronized***方法将vectory转为线程安全的Colection接口的子类,然后在来操作该方法返回的引用,其实现的原理是在底层给对象加锁。