同步容器类
同步容器类包括Vector和Hashtable,以及同步的封装容器类。它们实现线程安全类的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程访问容器的状态。
同步容器类的问题
容器上常见的复合操作:迭代、跳转(根据指定元素找到当前元素的下一个元素)以及条件运算("若没有则添加")。这些复合操作没有客户端加锁的情况下都是线程安全的,但是其他线程并发的修改容器时,它们可能就会出现意料之外的行为(报异常错误,但这个不是线程安全问题)。
import java.util.Vector;
import java.util.concurrent.TimeUnit;
public class UnsafeVectorHelpers {
public static Object getLast(Vector list) throws InterruptedException {
int lastIndex = list.size() - 1;
TimeUnit.NANOSECONDS.sleep(1);//使得线程睡眠1毫秒,为了能够得到想要的结果——报异常
System.out.println("getLast:" + lastIndex);
return list.get(lastIndex);
}
public static void deleteLast(Vector list) {
int lastIndex = list.size() - 1;
System.out.println("deleteLast:" + lastIndex);
list.remove(lastIndex);
}
public static void main(String[] args) {
final Vector<String> vector = new Vector<String>();
for (int i = 0; i < 10; i++) {
vector.add("value" +(i + 1));
}
new Thread(new Runnable() {//线程A
@Override
public void run() {
try {
UnsafeVectorHelpers.getLast(vector);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {//线程B
@Override
public void run() {
UnsafeVectorHelpers.deleteLast(vector);
}
}).start();
}
}
/*
运行结果输出:
deleteLast:9
getLast:9
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9
at java.util.Vector.get(Unknown Source)
at net.jcip.examples.buildingblock.UnsafeVectorHelpers.getLast(UnsafeVectorHelpers.java:11)
at net.jcip.examples.buildingblock.UnsafeVectorHelpers$1.run(UnsafeVectorHelpers.java:30)
at java.lang.Thread.run(Unknown Source)
*/
报异常了,为什么会出现这种情况呢?A线程在获得lastIndex的值(从输出的信息可以得出是9)的时候睡眠了,此时线程B执行deleteLast方法输出:deleteLast:9,并且删除了最后一个元素,那么此时的集合的容量大小就变成了9。A线程睡眠结束继续执行,lastIndex的值还是9,所以就造成了数组小标越界的异常。可以看出在并发访问线程安全集合类的时候有可能还是会出现问题。
为了避免上面的异常发生,可以使用客户端加锁来进行Vector的复合操作。
public class SafeVectorHelpers {
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);
}
}
}
使用了客户端加锁使得并发线程访问任意时刻都只有一个线程获得锁对Vector集合进行复核操作,这样就避免了上述问题发生。
迭代器与ConcurrentModificationException
容器的迭代现在也还是复合操作,如果在迭代期间同时有线程并发的修改这个容器,那么就会抛出一个ConcurrentModificationException异常。如果为了不发生这个异常可以在迭代的时候加锁来避免。当然想要不加锁,那么就可以拷贝一个容器的副本对这个副本进行迭代(拷贝的时候也要加锁)。
隐藏迭代器
必须要记住所有对共享容器的进行迭代的地方都需要加锁。容器的toString()方法、hashCode和equals方法也会间接的执行迭代操作,当容器作为另外一个容器的元素或键值时,就会出现这种情况。同样,containsAll、removeAll和retainAll等方法,以及把容器作为参数的构造函数,都会对容器进行迭代。所有这些间接迭代都可能抛出ConcurrentModificationException异常。