6. 并发集合类
Hashtable是一个易于使用,线程安全的集合类,它的线程安全性是凭借代价换来的--Hashtable中的所有方法都是同步的。HashMap是Hashtable的继承者,通过一个不同步的基类和一个同步的包装器Collections.synchronizedMap解决了线程安全性问题。通过将基本的功能从线程安全性中分离开来,Collections.synchronizedMap允许需要同步的用户可以拥有同步,而不需要同步的用户不需要为同步付出代价。Hashtable和Collections.synchronizedMap通过同步每个方法获得线程安全。这意味着当一个线程执行一个 Map 方法时,无论其他线程要对 Map 进行什么样操作,都不能执行,直到第一个线程结束才可以。 这样,就会有两个不足之处:首先,可伸缩性差,因为一次只能有一个线程可以访问hash表。其次,许多公用的混合操作仍需要外部同步,比如put-if-absent操作,必须要外部同步来保证数据的争用。java.util.concurrent 包添加了多个新的线程安全集合类(ConcurrentHashMap、CopyOnWriteArrayList 和 CopyOnWriteArraySet)。这些类的目的是提供高性能、高度可伸缩性、线程安全的基本集合类型版本。
6.1 ConcurrentHashMap类
ConcurrentHashMap类是对 Map 的线程安全的实现,比起 synchronizedMap 来,它提供了好得多的并发性。多个读操作几乎总可以并发地执行,ConcurrentHashMap的读操作并不需要加锁,是因为其HashEntry被定义为final(HashEntry代表每个hash链中的一个节点),而value被修饰为volatile的原因:
static final class HashEntry<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;
}
ConcurrentHashMap允许多个修改操作并发的执行,是因为其使用了锁分离技术,也就是说,它用多个锁来控制对hash表不同部分的修改操作。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行,其也被定义为final,保证了数据的不变性。
final Segment[] segments;
每当进行修改和删除的时候,首先判断是否在同一段内,只要不在同一段内,都可以进行并发的操作。
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, false);
}
public V remove(Object key) {
int hash = hash(key);
return segmentFor(hash).remove(key, hash, null);
}
ConcurrentHashMap返回的迭代器是弱一致的,并且返回的Iterators将每次最多返回一个元素,意味着它们将不抛出 ConcurrentModificationException。
final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K>
final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V>
final class EntryIterator extends HashIterator implements Map.Entry<K,V>,Iterator<Entry<K,V>>
6.2 CopyOnWriteArrayList和CopyOnWriteArraySet
CopyOnWriteArrayList和CopyOnWriteArraySet,适合于读操作通常大大超过写操作的情况。在CopyOnWriteArrayList 和 CopyOnWriteArraySet类中,所有可变的(mutable)操作都首先取得后台数组的副本,对副本进行更改,然后替换副本。这种做法保证了在遍历自身更改的集合时,永远不会抛出 ConcurrentModificationException 。遍历集合会用原来的集合完成,而在以后的操作中使用更新后的集合。
6.3 Queue与BlockingQueue
JDK 5.0 还提供了两个新集合接口 —— Queue 和 BlockingQueue。Queue继承自Collection,与 List 类似,但它只允许从后面插入,从前面删除。通过消除 List 的随机访问要求,可以创建比现有 ArrayList 和 LinkedList 实现性能更好的 Queue 实现。因为 List 的许多应用程序实际上不需要随机访问,所以Queue 通常可以替代 List,来获得更好的性能。其定义和方法如下:
public interface Queue<E> extends Collection<E> {
boolean offer(E o);
E poll();
E remove();
E peek();
E element();
}
一个队列就是一个先进先出FIFO的数据结构。队列有大小的限制,因此如果想在一个满的队列中继续添加元素,队列会拒绝次元素。如果此时调用的是add方法,那么会抛出异常。队里中的offer方法表示,如果向已满队列中继续添加元素,它会返回false。poll和remove方法都是从队列中删除第一个元素(head)。区别在于,如果队列为空,remove会抛出异常,而poll会返回null。peek和element用于在队列的头部查询元素,与remove方法相似,如果队列为空时,element抛出异常,peek返回null。
再复杂一点的是新的 java.util.AbstractQueue类。这个类的工作方式类似于 java.util.AbstractList和java.util.AbstractSet 类。在创建自定义集合时,不用自己实现整个接口,只是继承抽象实现并填入细节。使用 AbstractQueue 时,必须为方法 offer(),poll()和peek()提供实现。像 add()和addAll()这样的方法修改为使用offer(),而clear()和remove() 使用poll()。最后,element()使用 peek()。当然可以在子类中提供这些方法的优化实现,但是不是必须这么做。而且,不必创建自己的子类,可以使用几个内置的实现, 其中两个是不阻塞队列:PriorityQueue和ConcurrentLinkedQueue 。PriorityQueue和ConcurrentLinkedQueue类在Collection Framework中加入两个具体集合实现。PriorityQueue 类实质上维护了一个有序列表。加入到Queue中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)或者根据传递给构造函数的 java.util.Comparator实现来定位。ConcurrentLinkedQueue是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大小,ConcurrentLinkedQueue对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
BlockingQueue继承自Queue,添加了两个操作:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。其定义和新添加方法如下:
public interface BlockingQueue<E> extends Queue<E> {
E take() throws InterruptedException;
void put(E o) throws InterruptedException;
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
}
take表示当删除队列中第一个元素时,如果队列为空,会一直等待队列变为非空。put表示向已满队列添加元素,会一直等待队列空间变得可用。drainTo表示将队列中的元素添加到指定集合中,返回添加的个数。利用take和put方法,实现生产者和消费者:
class Producer implements Runnable {
private final BlockingQueue queue;
public Producer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
while (true) {
queue.put(produce());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private Object produce() {
return null;
}
}
class Consumer implements Runnable {
private final BlockingQueue queue;
public Consumer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
while (true) {
consume(queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void consume(Object x) {
//
}
}
BlockingQueue的5个实现类分别为:
ArrayBlockingQueue: 一个由数组支持的有界队列。
LinkedBlockingQueue: 一个由链接节点支持的可选有界队列。
PriorityBlockingQueue: 一个由优先级堆支持的无界优先级队列。
DelayQueue: 一个由优先级堆支持的、基于时间的调度队列。
SynchronousQueue: 一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。
ArrayBlockingQueue和LinkedBlockingQueue几乎相同,只是在后备存储器方面有所不同,ArrayBlockingQueue为数组存储,LinkedBlockingQueue为链接节点存储,LinkedBlockingQueue中的头元素为在队列中时间最长的元素。LinkedBlockingQueue 并不总是有容量界限。无大小界限的 LinkedBlockingQueue 类在添加元素时永远不会有阻塞队列的等待(至少在其中有 Integer.MAX_VALUE 元素之前不会)。
PriorityBlockingQueue是具有无界限容量的队列,它利用所包含元素的Comparable排序顺序来以逻辑顺序维护元素。可以将它看作TreeSet的可能替代物。例如,在队列中加入字符串One,Two,Three和Four会导致Four被第一个取出来。对于没有天然顺序的元素,可以为构造函数提供一个Comparator 。不过对PriorityBlockingQueue有一个技巧。从iterator()返回的Iterator实例不需要以优先级顺序返回元素。如果必须以优先级顺序遍历所有元素,那么让它们都通过 toArray()方法并自己对它们排序,像 Arrays.sort(pq.toArray()) 。
DelayQueue的定义如下,所以像其中添加的元素必须实现Delay接口(只有一个方法long getDelay(TimeUnit unit)):
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E>
因为队列的大小没有界限,使得添加可以立即返回,但是在延迟时间过去之前,不能从队列中取出元素。如果多个元素完成了延迟,那么最早失效/失效时间最长的元素将第一个取出。
SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。
分享到:
相关推荐
Concurrent Programming in Java Design Principles and Pattern英文版 2.48M Java并发编程设计原则与模式_第二版(原书中文版) 19.4M Concurrent_Programming_in_Java_Design_Principles_Lecture DougLea
详细的java 多线程相关知识 并附有相关练习题
Concurrent Programming in Java - Design Principles and Patterns
java concurrent 多线程 PPT
concurrent programming in java design principles and patterns .chm
Doug Lea, Concurrent Programming in Java Design Principles and Patterns
详细介绍java多线程编程的各个基础概念。JUC作者doug lea著
在并发或多线程应用程序中使用Java编程语言的简介。
word版本的资料,网上...Concurrent Programming in Java™: Design Principles and Patterns, Second Edition Doug Lea Publisher: Addison Wesley Second Edition October 01, 1999 ISBN: 0-201-31009-0, 432 pages
Doug Lea Java Concurrent Programming
This book shows readers how to use the Java platform's threading model more precisely by helping them to understand the patterns and tradeoffs associated with concurrent programming
Concurrent - Programming in Java.pdf,ppt,Doug Lea
Concurrent Programming on Windows 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系...
Concurrent Programming in Java™: Design Principles and Patterns, Second Edition. 介绍并发编程的好的著作,著名的并发大师 Doug Lea的杰作。
Title: Learning Concurrent Programming in Scala, 2nd Edition Author: Aleksandar Prokopec Length: 382 pages Edition: 2nd Revised edition Language: English Publisher: Packt Publishing - ebooks Account ...
Java并发编程-设计原则与模式(Concurrent.Programming.in.Java-Design.Principles.and.Patterns(Second.Edition))(中英版)
Concurrent Programming in Java Thread 看看吧
资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...