java并发容器(Map、List、BlockingQueue)具体解释
Java库本身就有多种线程安全的容器和同步工具,当中同步容器包含两部分:一个是Vector和Hashtable。另外还有JDK1.2中增加的同步包装类。这些类都是由Collections.synchronizedXXX工厂方法。
同步容器都是线程安全的,可是对于复合操作。缺有些缺点:
① 迭代:在查觉到容器在迭代開始以后被改动,会抛出一个未检查异常ConcurrentModificationEx
② 隐藏迭代器:StringBuilder的toString方法会通过迭代容器中的每一个元素,另外容器的hashCode和equals方法也会间接地调用迭代。
类似地,contailAll、removeAll、retainAll方法。以及容器作为參数的构造函数,都会对容器进行迭代。
③ 缺少即增加等一些复合操作
public
int
return
}
public
int
list.remove(lastIndex);
}
getLast和deleteLast都是复合操作。由先前对原子性的分析能够推断,这依旧存在线程安全问题。有可能会抛出ArrayIndexOutOfBoundsExc
解决的方法就是通过对这些复合操作加锁
1 并发容器类
正是因为同步容器类有以上问题,导致这些类成了鸡肋,于是Java
1.1 ConcurrentHashMap
·
·
·
·
·
putIfAbsent(K
类似于以下的操作
If(!map.containsKey(key)){
return
}else{
return
}
remove(Object
类似于以下的:
if(map.containsKey(key)
Map.remove();
return
}else{
return
}
replace(K
上面提到的三个。都是原子的。在一些缓存应用中能够考虑取代HashMap/Hashtable。
1.2 CopyOnWriteArrayList和CopyOnWriteArraySet
·
应用它们的场合通常在数组相对较小。而且遍历操作的数量大大超过可变操作的数量时。这样的场合应用它们很好。它们全部可变的操作都是先取得后台数组的副本,对副本进行更改。然后替换副本。这样能够保证永远不会抛出ConcurrentModificationEx
2 队列
Java中的队列接口就是Queue。它有会抛出异常的add、remove方法。在队尾插入元素以及对头移除元素。还有不会抛出异常的,相应的offer、poll方法。
2.1 LinkedList
List实现了deque接口以及List接口,能够将它看做是这两种的不论什么一种。
Queue
queue.offer("testone");
queue.offer("testtwo");
queue.offer("testthree");
queue.offer("testfour");
System.out.println(queue.poll());//testone
2.2 PriorityQueue
一个基于优先级堆(简单的使用链表的话。可能插入的效率会比較低O(N))的无界优先级队列。
优先级队列的元素依照其自然顺序进行排序,或者依据构造队列时提供的
优先级队列不同意使用
queue=new
queue.offer("testone");
queue.offer("testtwo");
queue.offer("testthree");
queue.offer("testfour");
System.out.println(queue.poll());//testfour
2.3 ConcurrentLinkedQueue
基于链节点的。线程安全的队列。
并发訪问不须要同步。在队列的尾部加入元素,并在头部删除他们。
所以仅仅要不须要知道队列的大小。并发队列就是比較好的选择。
3 堵塞队列
3.1 生产者和消费者模式
生产者和消费者模式,生产者不须要知道消费者的身份或者数量,甚至根本没有消费者。他们仅仅负责把数据放入队列。
类似地。消费者也不须要知道生产者是谁,以及是谁给他们安排的工作。
而Java知道大家清楚这个模式的并发复杂性,于是乎提供了堵塞队列(BlockingQueue)来满足这个模式的需求。堵塞队列说起来非常easy,就是当队满的时候写线程会等待,直到队列不满的时候;当队空的时候读线程会等待。直到队不空的时候。实现这样的模式的方法非常多,其差别也就在于谁的消耗更低和等待的策略更优。以LinkedBlockingQueue的详细实现为例,它的put源代码例如以下:
public
//
}
撇开其锁的详细实现,其流程就是我们在操作系统课上学习到的标准生产者模式,看来那些枯燥的理论还是实用武之地的。当中,最核心的还是Java的锁实现,有兴趣的朋友能够再进一步深究一下。
堵塞队列Blocking
3.2 ArrayBlockingQueue和LinkedBlockingQueue
FIFO的队列。与LinkedList(由链节点支持。无界)和ArrayList(由数组支持。有界)相似(Linked有更好的插入和移除性能。Array有更好的查找性能,考虑到堵塞队列的特性,移除头部,增加尾部,两个都差别不大)。可是却拥有比同步List更好的并发性能。
另外,LinkedList永远不会等待,由于他是无界的。
BlockingQueue<String>
Producer
Consumer
Consumer
new
new
new
class
class
输出:
produceK
cousumeK
produceV
cousumeV
produceQ
cousumeQ
produceI
produceD
produceI
produceG
produceA
produceE
cousumeD
3.3 PriorityBlockingQueue
一个按优先级堆支持的无界优先级队列队列,假设不希望依照FIFO的顺序进行处理,它很实用。
它能够比較元素本身的自然顺序。也能够使用一个Comparator排序。
3.4 DelayQueue
一个优先级堆支持的。基于时间的调度队列。加入到队列中的元素必须实现新的Delayed接口(仅仅有一个方法。Long
static
long
NanoDelay(long
tigger=System.nanoTime()+i;
}
public
return
}
public
long
return
}
public
return
}
public
long
long
if(i<j){
return
}
if(i>j)
return
return
}
}
public
Random
DelayQueue<NanoDelay>
for(int
queue.add(new
}
long
for(int
NanoDelay
long
System.out.println("Trigger
if(i!=0){
System.out.println("Data:
}
last=tt;
}
}
3.5 SynchronousQueue
不是一个真正的队列,由于它不会为队列元素维护不论什么存储空间,只是它维护一个排队的线程清单。这些线程等待把元素增加(enqueue)队列或者移出(dequeue)队列。
也就是说。它很直接的移交工作,降低了生产者和消费者之间移动数据的延迟时间,另外。也能够更快的知道反馈信息,当移交被接受时,它就知道消费者已经得到了任务。
由于SynChronousQueue没有存储的能力。所以除非还有一个线程已经做好准备,否则put和take会一直阻止。它仅仅有在消费者比較充足的时候比較合适。
4 双端队列(Deque)
JAVA6中新增了两个容器Deque和BlockingDeque,他们分别扩展了Queue和BlockingQueue。Deque它是一个双端队列,同意高效的在头和尾分别进行插入和删除。它的实现各自是ArrayDeque和LinkedBlockingQueue。
双端队列使得他们可以工作在一种称为“窃取工作”的模式上面。
5 最佳实践
1..同步的(synchronized)+HashMap,假设不存在,则计算,然后增加,该方法须要同步。
HashMap
public
V
Ii(result==null){
result=c.compute(arg);
Cache.put(result);
}
Return
}
2.用ConcurrentHashMap取代HashMap+同步.。这种在get和set的时候也基本能保证原子性。可是会带来反复计算的问题.
Map<A,V>
public
V
Ii(result==null){
result=c.compute(arg);
Cache.put(result);
}
Return
}
3.採用FutureTask取代直接存储值,这样能够在一開始创建的时候就将Task增加
Map<A,FutureTask<V>>
public
FutureTask
//检查再执行的缺陷
Ii(f==null){
Callable<V>
Public
return
}
};
FutureTask
f=ft;
cache.put(arg,ft;
ft.run();
}
Try{
//堵塞,直到完毕
return
}cach(){
}
}
4.上面还有检查再执行的缺陷,在高并发的情况下啊,两方都没发现FutureTask,而且都放入Map(后一个被前一个替代),都開始了计算。
这里的解决方式在于。当他们都要放入Map的时候。假设能够有原子方法。那么已经有了以后,后一个FutureTask就增加,而且启动。
public
FutureTask
//检查再执行的缺陷
Ii(f==null){
Callable<V>
Public
return
}
};
FutureTask
f=cache.putIfAbsent(args,ft); //假设已经存在。返回存在的值。否则返回null
if(f==null){f=ft;ft.run();} //曾经不存在。说明应该開始这个计算
else{ft=null;} //取消该计算
}
Try{
//堵塞,直到完毕
return
}cach(){
}
}
5.上面的程序上来看已经完美了。只是可能带来缓存污染的可能性。假设一个计算被取消或者失败,那么这个键以后的值永远都是失败了;一种解决方式是,发现取消或者失败的task,就移除它,假设有Exception,也移除。
6.另外,假设考虑缓存过期的问题,能够为每一个结果关联一个过去时间。并周期性的扫描,清除过期的缓存。
(过期时间能够用Delayed接口实现,參考DelayQueue,给他一个大于当前时间XXX的时间,,而且不断减去当前时间,直到返回负数,说明延迟时间已到了。
)
List
Map接口是一组成对的键-值对象,即所持有的是key-value
Map中不能有反复的key。拥有自己的内部排列机制
Java1.2前的容器:Vector,Stack,Hashtable。
Java1.2的容器:HashSet,TreeSet。HashMap。TreeMap,WeakHashMap
Java1.4的容器:LinkedHashSet,LinkedHashMap,IdentityHashMap,ConcurrentMap。concurrentHashMap
java1.5新增:CopyOnWriteArrayList,AttributeList,RoleList,RoleUnresolvedList,
未知:JobStateReasons
一。
二。
一,
注意1:concurrentHashMap迭代器它们不会抛出ConcurrentModificationEx
只是,迭代器被设计成每次仅由一个线程使用。
二,
list容器:ArrayList,LinkedList,AttributeList,RoleList,RoleUnresolvedList
set容器:HashSet,TreeSet,TreeSet,LinkedHashSet
map容器:HashMap。TreeMap,LinkedHashMap,WeakHashMap,IdentityHashMap
不管怎样
list容器:PriorityQueue(1.5)。PriorityBlockingQueue
set容器:TreeSet(1.2)
map容器:TreeMap(1.2)
RandomAccess接口是List
在对List特别的遍历算法中。要尽量来推断是属于
由于适合RandomAccess
即对于实现了RandomAccess接口的类实例而言,此循环
的执行速度要快于下面循环: