1.同步容器类
它们是线程安全的
1.1 vector和hashtable。
和Collections.synchronizeXxx()一样。实现方式就是在每个方法里面加入synchronize代码块包着。加锁对象为当前对象
public int hashCode() {
// ...
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
// ...
但对于复合操作,还是有风险。例如有两个线程分别执行:检查-获取元素,检查-删除元素。那么就会出现异常了。通常解决的方法是将检查-获取元素,检查-删除元素这样的操作进行synchronize代码块操作。而在遍历列表的情况下,通常可以考虑复制出一份元素在当前线程进行操作。防止其他线程修改元素导致ArrayIndexOutOfBoundException。
1.2 ConcurrentHashMap
它采用了和其他同步容器类不同的实现方式。一种粗粒度更细的锁:分段锁。concurrenthashmap的分段锁的方式带来了更高的性能,但是也权重放弃了一些东西。比如size。和isempty()。(concurrenthashmap需要单独篇幅介绍。暂时只介绍他对于并发的基础知识)。
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
可以看出size并没有一个全局的量去给调用方获取。而是在每次调用的时候重新计算size,而size的counterCells是在每次修改元素后会进行修改该数组。也就是会带来size返回的可能是有偏差的值,这在高并发的操作中size的用处很小,因为他们的值总在变化。牺牲这些值来获取其他常用方法的更高性能。
总的来说:concurrentHashmap对比Collections.synchronizeHashmap和HashTable来说。一般情况下都优先使用concurrenthashmap。并发状况下具有更高的性能优势和更少的劣势。