首先了解同步容器,同步容器将所有的容器状态的访问都实现串行化,以保证线程安全性,比如:HashMap,List,Map,List等等,这种方法代价严重降低并发性,当多个线程竞争容器锁时,吞吐量会严重降低,JAVA5就提供了多种并发容器来改进同步容器的性能。例如:ComcurrentHashMap,CopyOnWriteArrayList
,ConcurrentMap,ConcurrentLinkedQueue。BlockingQueue等,介绍三种。
1.ConcurrentHashMap
同步容器类在执行每个操作的期间都持有一个锁,在以下操作中HashMap.get后Lint.contains,可能包括大量工作:当遍历散列桶或链表来查找某个元素时,如果hash不是均匀分布,可能要多次调用equal方法,而对链表调用contains方法时,要遍历整个链表,这样会花费很长的时间,在这段时间内其他线程是不能访问这个容器的,这样极大影响了并发性。而ConcurrentHashMap虽然也是基于散列的map,但他使用了一种完全不同的加锁策略来提供并发性,ConcurrentHashMap并不把所有的方法都在同一个锁上进行同步并使只能有一个线程访问容器。而是使用一种粒度更细的加锁机制来实现更大程度的共享。与HashTable和synchronizedMap相比,ConcurrentHaspMap具有更多的优势,只有在程序需要加锁map以进行独占访问时,我才放弃使用ConcurrentHashMap。
ConcurrentHashMap接口
package java.util.concurrent;
import java.util.Map;
/**
* @since 1.5
* @author Doug Lea
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public interface ConcurrentMap<K, V> extends Map<K, V> {
//如果K没有对应的映射值 才插入
V putIfAbsent(K key, V value);
//仅当K值被映射到V时才移除
boolean remove(Object key, Object value);
//仅当K被映射到oldValue时才替换newValue
boolean replace(K key, V oldValue, V newValue);
//仅当K值被映射到V时才移除 替换
V replace(K key, V value);
}
2.CopyOnWriteListArrayList
它用于替代同步list,在某些情况下能够提供更好的并发性能,从字面意思可以看出,在每次修改时,都会创建并重新发布一个新的容器版本,从而实现可变性,“写入时复制”容器的迭代器保留一个执行底层基础数组的引用,这个数组当前位于迭代器的其实位置,由于它不会被修改,一次在对其进行同步时只需确保数组内容的可见性。一次多线程可以同时对这个容器进行迭代,而不会彼此干扰或者与修改容器的线程相互干扰。很显然,每次修改容器都要复制底层数组,这需要一定的开销,特别是当容器的规模较大时。因此,他的应用场景主要在迭代操作远远多余修改操作时,一些事件监听系统,在分发通知都需要迭代已经注册监听器表,并且调用每个监听器,而注册新的监听器和取消监听器这种操作很少,这种场景就非常适合使用使用CopyOnWriteListArrayList。
3.BlockingQueue 实现(LinkedBlockingQueue,ArrayBlockinQueue,SynchronousQueue)
阻塞队列,提供了可阻塞的put和take方法,如果队列慢了,那么put就会阻塞,直到队列有空间可用,如果队列为空,那么take方法就会阻塞,直到队列有元素可用,可以利用阻塞队列实现生产者-和消费者模式。
引用场景,使用多线程处理批量任务时候,可使用BlockingQueue,这里比较特使的SynchronousQueue,
它实际上不是一个真正的队列,因为不会为队列内元素维护存储空间,与其他队列不同的是,它维护一组线程,而这些线程等待把元素加入和移除队列,这样就没有入列和出列的操作了,而这个队列put,take会一直阻塞,直到另一个线程准备好获取交付的工作时
附加一个文件模拟搜索程序demo:
生产者遍历文件并加入队列,消费者取出文件名称对它建立索引便于后面访问。生产者遍历文件
public class FileSearch implements Runnable {
private final BlockingQueue<File> fileQueue;
private final FileFilter fileFilter;
private final File root;
....
public void run() {
try {
search(root);
}catch (InterruptedException e){
//恢复被中断的状态
Thread.currentThread().interrupt();
}
}
private void search(File root){
File[] entries = root.listFiles(fileFilter);
if (entries!=null){
for(File entry :entries){
if(entry.isDirectory()){
search(root);
}else if(!alreadyIndexed(entry)){
fileQueue.put(entry);
}
}
}
}
}
消费者加索引
public class Indexerimplements Runnable {
private final BlockingQueue<File> queue;
public Indexer(BlockingQueue<File> queue){
this.queue=queue;
}
public void run() {
try {
while (true){
indexFile(queue.take());
}
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
}
}
以上两段伪代码,是对阻塞队列的使用