Java(8-5)阻塞队列与线程安全集合

来源:互联网 发布:mac口红国内专柜价格表 编辑:程序博客网 时间:2024/06/03 20:07

上一次,我们讨论了java多线程的一些细节问题,包括死锁,读写锁等等。这一节,我们要介绍关于阻塞队列以及如何使用并且操作线程安全的集合 。

Part 1 阻塞队列

对于许多线程问题,可以通过一个或多个队列以优雅且安全的方式将其形式化。

想一下,生产者线程向队列插入元素,消费者线程取出他们。使用队列可以安全地从一个线程向另一个线程传递数据。例如,我们从银行转账,转账线程将转账转账指令对象(命令模式?) 插入到一个队列中,而不是直接访问银行对象。另一个线程从队列中取出指令执行转账。只有该线程可以访问该银行系统的内部。因此不需要同步(当然,线程安全的队列类的不能不考虑锁和条件,但是,那是他们的问题。)

当试图向队列中添加元素而队列已满,或是想从队列中移出元素而队列为空的时候,阻塞队列导致线程阻塞。

在协调多个线程之间的合作时,阻塞队列是一个有用的工具。 工作者线程可以周期性地将中间结果存储在阻塞队列中。其他的工作者线程移出中间结果并进一步加以修改。队列会自动地平衡负载。

阻塞队列具有如下方法:
add 添加一个元素 如果队列慢,就抛出IllegalStateException异常

element 返回队列的头元素 如果队列空,抛出NoSuchElementException异常

offer 添加一个元素并返回true 如果队列满,则返回false

peek 返回队列的头元素 如果队列空则返回null

poll 移出并返回队列的头元素 如果队列空,则返回null

put 添加一个元素 如果队列满,则阻塞

remove 移出并返回头元素 如果队列空,则抛出NoSuchElementException

take 移出并返回头元素 如果队列空,则阻塞

从方法中我们可以发现,设计者是希望我们将阻塞队列的方法分为如下:
1.如果将队列当成线程管理工具来使用,将要用到put和take方法;
2.当试图向一个满的队列添加或者从空的队列中移出元素的时候,add,remove,element方法操作会抛出异常,但是在多线程中,队列可能在任何时候变空,或者变满,因此一定要使用offer, poll 和 peek 方法替代!。这样子他就只是给出一个错误提示,而不是抛出一个异常。

建议阅读《java核心技术卷Ⅰ》中 P670 的代码 ,加深理解 。

Part 2 线程安全的集合

如果多线程要并发地修改一个数据结构,例如散列表,那么很容易会破坏这个数据结构。 举个例子:一个线程可能向表中插入一个新的元素,假定在调整散列表各个桶之间的链接关系的过程之中,被剥夺了控制权。如果另一个线程也开始遍历同一个同一个链表,很可能无法认出已经在调整之中的链接(可能已经无效了)并造成混乱从而抛出异常。

可以通过提供锁来保护共享数据结构,但是选择线程安全的实现作为替代可能更容易一些。当然,前一节讨论的阻塞队列就是线程安全的集合。在下面各小节中,将讨论Java类库提供的另外一些线程安全的集合 。

Part 2.1 高效的映射、集和队列

java.util.concurrent包提供了映射、有序集、和队列的高效实现,通过复杂的算法,允许并发的访问数据结构的不同部分来让竞争最小化,他们是:ConcurrentHashMap , ConcurrentSkipListMap , ConcurrentSkipListSet , 和 ConcurrentLinkedQueue 。

Part 2.2 并发试图集
假设你想要的是一个大的线程安全的集而不是映射。我们可以这样做:

Set<String> words = ConcurrentHashMap.<String>newKeySet();

静态的newKeySet方法会生成一个Set < k >,实际上是是一个包装器,所有的映射值都为Boolean.TRUE,我们把他作为一个集,不关心他的具体值。

如果在一个线程可能进行修改时要对集合进行迭代,仍然需要使用客户端上锁,就是说对集合的操作方法上锁

原创粉丝点击