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 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。
分享到:
相关推荐
为了简化多线程编程,Java提供了一系列工具和API,如`java.util.Timer`和`java.util.concurrent`包,这些工具可以帮助开发者更高效地管理线程间的同步问题。 ##### 1.2 synchronized关键字 `synchronized`关键字是...
concurrent programming in java design principles and patterns .chm
Java的并发库提供了一系列工具和API,如`java.util.concurrent`包,帮助开发者有效地管理并发任务。本书主要涵盖以下几个方面: 1. **线程基础**:书中首先介绍了线程的基本概念,包括如何创建和管理线程,以及线程...
Java 1.5引入了`java.util.concurrent`包,包含了一系列的并发工具类,如线程池、阻塞队列、并发集合等。这些工具旨在提高并发性能并简化编程模型。例如,`ExecutorService`和`ThreadPoolExecutor`提供了线程池管理...
《Doug Lea, Concurrent Programming in Java Design Principles and Patterns》是一本深入探讨Java并发编程的经典著作,由Doug Lea撰写。这本书对于理解Java平台上的多线程编程和并发设计模式至关重要,是许多Java...
Java平台提供了丰富的API支持并发编程,如`java.util.concurrent`包下的各种类和接口,这些工具可以帮助开发者更高效地管理多线程环境下的任务调度和数据共享问题。 ### Java并发编程基础 #### 1. 多线程基础 - **...
《Java并发编程》一书是由著名并发编程专家Doug Lea所著,他同时也是Java并发包(JUC)的作者,这本书详细介绍了Java多线程编程的基础概念和高级技术。 首先,书中提到了并发编程的基本概念,包括并发模型、设计力...
本书《Concurrent Programming in Java™: Design Principles and Patterns 2nd》由Doug Lea编写,出版于1999年,是关于Java并发编程的一本权威指南。Java平台因其强大的线程支持能力而备受青睐,这使得Java程序员...
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并发编程实践》第二版是一本专注于Java编程语言中并发程序设计的著作,由Doug Lea撰写,Addison Wesley出版社于1999年10月出版。这本书旨在为那些熟悉面向对象编程但对并发编程了解不多的开发者提供指导,同时...
例如,使用无锁数据结构或原子操作(`java.util.concurrent.atomic`包)。 3. **避免死锁、活锁和饥饿**:理解并预防这些并发问题至关重要。死锁发生在两个或多个线程相互等待对方释放资源导致僵局;活锁是线程不断...
Concurrent Programming in Java Design Principles and Pattern英文版 2.48M Java并发编程设计原则与模式_第二版(原书中文版) 19.4M Concurrent_Programming_in_Java_Design_Principles_Lecture DougLea
- **标准库**:Java并发工具包(java.util.concurrent)提供了丰富的并发工具类。 - **第三方库**:例如Apache Commons Concurrency等。 ##### 2. 构建库 - **自定义并发组件**:根据项目需求开发特定的并发工具。 ...
《Concepts and Notations for Concurrent Programming》这篇论文由Gregory R. Andrews和Fred B. Schneider撰写,深入探讨了并行编程的核心概念和技术。尽管这是一篇较为古老的文章,但其内容仍然具有很高的参考价值...
资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...