上篇博客中介绍的是同步容器,通过上篇博客我们对java平台中的同步容器类有所了解,这篇博客介绍一下并发容器。
在JAVA5.0以后就开始提供了并发容器类来改进同步容器的性能。同步容器将所有对容器状态的访问都串行化,以实现它们的线程安全,这种方法是严重降低并发性,当多个线程竞争容器的锁时候,吞吐量会大大降低。
在JAVA5.0之后增加了ConcurrentHashMap代替同步基于算列的Map,以及CopyOnWriteArrayList替代同步的List。
此外还增加了两个新的容器的类型:Queue和BlockingQueue。Queue用来临时保存一组等待处理的元素。它有几种实现包括ConcurrentLinkedQueue和PriorityQueue。
而 BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空时,则获取元素的操作会一直阻塞,直到队列中出现一个可用的元素,如果队列已经满了,那么插入元素的操作将一直阻塞,直到队列中出现可用的空间。这种容器在生产者和消费者模型中是经常用到的。
通过一个比较经典的例子来看一下并发容器使用。
public interface Computable<A,V>{
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computalbe<String ,BigInteger>{
public BigInteger compute(String arg){
return new BigInteger(arg);
}
}
public class Memoizer1<A,V> implements Computable<A,V>{
private final Map<A,V> cache=new HashMap<A,V>();
private final Computable<A,V> c;
public Memoizer1(Computable<A,V>,c){
this.c=c;
}
public synchronized V compute(A arg) throws InterruptedException{
V result=cache.get(arg);
if(result==null){
result=c.compute(arg);
cache.put(arg,result);
}
return result;
}
}
这个例子就是关于缓存的,缓存在几乎所有的应用服务器中都有应用。缓存带来的好处就是降低延迟、提高吞吐量,但它消耗了更多的内存。
上面的代码使用的是HashMap来保存之前计算结果,compute方法将首先检查需要的结果是不是在缓存中,如果存在则返回之前的计算值,否则把计算结果缓存在HashMap中,然后返回。
因为HashMap不是线程安全的,所以当两个不同线程访问的时候,Memoizer1采用了一种保守的方法,对compute方法进行同步,这种方法确实能够保证线程安全,但是带来了一个明显的问题就是每次只有一个线程执行compute,这种情况就会出现阻塞时间。
下篇博客来改进一下这个程序。