参考资料
http://www.ibm.com/developerworks/cn/java/j-5things4.html
Concurrent Collections 是 Java™ 5 的巨大附加产品,但是在关于注释和泛型的争执中很多 Java
开发人员忽视了它们。此外(或者更老实地说),许多开发人员避免使用这个数据包,因为他们认为它一定很复杂,就像它所要解决的问题一样。
事实上,java.util.concurrent
包含许多类,能够有效解决普通的并发问题,无需复杂工序。阅读本文,了解
java.util.concurrent
类,比如 CopyOnWriteArrayList
和
BlockingQueue
如何帮助您解决多线程编程的棘手问题。
1. TimeUnit
尽管本质上 不是 Collections 类,但 java.util.concurrent.TimeUnit
枚举让代码更易读懂。使用 TimeUnit
将使用您的方法或 API 的开发人员从毫秒的 “暴政” 中解放出来。
TimeUnit
包括所有时间单位,从 MILLISECONDS
和
MICROSECONDS
到 DAYS
和
HOURS
,这就意味着它能够处理一个开发人员所需的几乎所有的时间范围类型。同时,因为在列举上声明了转换方法,在时间加快时,将
HOURS
转换回 MILLISECONDS
甚至变得更容易。
回页首
2. CopyOnWriteArrayList
创建数组的全新副本是过于昂贵的操作,无论是从时间上,还是从内存开销上,因此在通常使用中很少考虑;开发人员往往求助于使用同步的
ArrayList
。然而,这也是一个成本较高的选择,因为每当您跨集合内容进行迭代时,您就不得不同步所有操作,包括读和写,以此保证一致性。
这又让成本结构回到这样一个场景:需多读者都在读取 ArrayList
,但是几乎没人会去修改它。
CopyOnWriteArrayList
是个巧妙的小宝贝,能解决这一问题。它的 Javadoc 将
CopyOnWriteArrayList
定义为一个 “ArrayList
的线程安全变体,在这个变体中所有易变操作(添加,设置等)可以通过复制全新的数组来实现”。
集合从内部将它的内容复制到一个没有修改的新数组,这样读者访问数组内容时就不会产生同步成本(因为他们从来不是在易变数据上操作)。
本质上讲,CopyOnWriteArrayList
很适合处理 ArrayList
经常让我们失败的这种场景:读取频繁,但很少有写操作的集合,例如 JavaBean 事件的 Listener
s。
回页首
3. BlockingQueue
BlockingQueue
接口表示它是一个
Queue
,意思是它的项以先入先出(FIFO)顺序存储。在特定顺序插入的项以相同的顺序检索 —
但是需要附加保证,从空队列检索一个项的任何尝试都会阻塞调用线程,直到这个项准备好被检索。同理,想要将一个项插入到满队列的尝试也会导致阻塞调用线程,直到队列的存储空间可用。
BlockingQueue
干净利落地解决了如何将一个线程收集的项“传递”给另一线程用于处理的问题,无需考虑同步问题。Java
Tutorial 的 Guarded Blocks
试用版就是一个很好的例子。它构建一个单插槽绑定的缓存,当新的项可用,而且插槽也准备好接受新的项时,使用手动同步和
wait()
/notifyAll()
在线程之间发信。(详见 Guarded
Blocks 实现。)
尽管 Guarded Blocks 教程中的代码有效,但是它耗时久,混乱,而且也并非完全直观。退回到 Java 平台较早的时候,没错,Java
开发人员不得不纠缠于这种代码;但现在是 2010 年 — 情况难道没有改善?
清单 1 显示了 Guarded Blocks 代码的重写版,其中我使用了一个
ArrayBlockingQueue
,而不是手写的 Drop
。
清单 1. BlockingQueue
import java.util.*;
import java.util.concurrent.*;
class Producer
implements Runnable
{
private BlockingQueue<String> drop;
List<String> messages = Arrays.asList(
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"Wouldn't you eat ivy too?");
public Producer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
for (String s : messages)
drop.put(s);
drop.put("DONE");
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
class Consumer
implements Runnable
{
private BlockingQueue<String> drop;
public Consumer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(msg);
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
public class ABQApp
{
public static void main(String[] args)
{
BlockingQueue<String> drop = new ArrayBlockingQueue(1, true);
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
|
ArrayBlockingQueue
还体现了“公平” —
意思是它为读取器和编写器提供线程先入先出访问。这种替代方法是一个更有效,但又冒穷尽部分线程风险的政策。(即,允许一些读取器在其他读取器锁定时运行效率更高,但是您可能会有读取器线程的流持续不断的风险,导致编写器无法进行工作。)
BlockingQueue
还支持接收时间参数的方法,时间参数表明线程在返回信号故障以插入或者检索有关项之前需要阻塞的时间。这么做会避免非绑定的等待,这对一个生产系统是致命的,因为一个非绑定的等待会很容易导致需要重启的系统挂起。
回页首
4. ConcurrentMap
Map
有一个微妙的并发 bug,这个 bug 将许多不知情的 Java
开发人员引入歧途。ConcurrentMap
是最容易的解决方案。
当一个 Map
被从多个线程访问时,通常使用 containsKey()
或者
get()
来查看给定键是否在存储键/值对之前出现。但是即使有一个同步的
Map
,线程还是可以在这个过程中潜入,然后夺取对 Map
的控制权。问题是,在对
put()
的调用中,锁在 get()
开始时获取,然后在可以再次获取锁之前释放。它的结果是个竞争条件:这是两个线程之间的竞争,结果也会因谁先运行而不同。
如果两个线程几乎同时调用一个方法,两者都会进行测试,调用 put,在处理中丢失第一线程的值。幸运的是,ConcurrentMap
接口支持许多附加方法,它们设计用于在一个锁下进行两个任务:putIfAbsent()
,例如,首先进行测试,然后仅当键没有存储在
Map
中时进行 put。
回页首
5. SynchronousQueues
根据 Javadoc,SynchronousQueue
是个有趣的东西:
这是一个阻塞队列,其中,每个插入操作必须等待另一个线程的对应移除操作,反之亦然。一个同步队列不具有任何内部容量,甚至不具有 1 的容量。
本质上讲,SynchronousQueue
是之前提过的 BlockingQueue
的又一实现。它给我们提供了在线程之间交换单一元素的极轻量级方法,使用 ArrayBlockingQueue
使用的阻塞语义。在清单 2
中,我重写了 清单 1 的代码,使用 SynchronousQueue
替代
ArrayBlockingQueue
:
清单 2.
SynchronousQueue
import java.util.*;
import java.util.concurrent.*;
class Producer
implements Runnable
{
private BlockingQueue<String> drop;
List<String> messages = Arrays.asList(
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"Wouldn't you eat ivy too?");
public Producer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
for (String s : messages)
drop.put(s);
drop.put("DONE");
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
class Consumer
implements Runnable
{
private BlockingQueue<String> drop;
public Consumer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(msg);
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
public class SynQApp
{
public static void main(String[] args)
{
BlockingQueue<String> drop = new SynchronousQueue<String>();
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
|
实现代码看起来几乎相同,但是应用程序有额外获益:SynchronousQueue
允许在队列进行一个插入,只要有一个线程等着使用它。
在实践中,SynchronousQueue
类似于 Ada 和 CSP 等语言中可用的
“会合通道”。这些通道有时在其他环境中也称为 “连接”,这样的环境包括 .NET (见 参考资料)。
回页首
结束语
当 Java 运行时知识库提供便利、预置的并发性时,为什么还要苦苦挣扎,试图将并发性导入到您的 Collections
类?本系列的下一篇文章将会进一步探讨 java.util.concurrent
名称空间的内容。
<!-- CMA ID: 498124 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->
分享到:
相关推荐
### Java并发工具包 `java.util.concurrent` 知识点详解 #### 一、引言 随着多核处理器的普及和应用程序复杂度的增加,多线程编程成为了现代软件开发不可或缺的一部分。为了简化并发编程的复杂性,Java 5 引入了 `...
Java并发工具包(java.util.concurrent)是Java平台上用于高效、安全地处理多线程编程的重要组件。这个包包含了丰富的并发工具类,旨在帮助开发者构建高度并发的程序,提高程序的性能和可伸缩性。本资源是该工具包的...
Java.util.concurrent是Java 5.0引入的一个重要包,它为多线程编程提供了一组高级并发工具。这个包的设计者是Doug Lea,它的出现是JSR-166的一部分,也被称作Tiger更新。Java.util.concurrent的引入是为了解决传统...
`java.util.concurrent`包是Java提供的一个强大的多线程工具库,其中包含了许多类和接口,如`CountDownLatch`和`CyclicBarrier`,它们为程序员提供了更高级别的同步和协调机制。这篇文档将详细解析这两个工具类的...
标题中提到了“java.util.concurrent.uml.pdf”,这表明文件是一份Java并发编程工具包java.util.concurrent的UML(统一建模语言)类结构图的PDF格式文件。UML图能够帮助开发者理解Java并发包中的类、接口及其关系,...
Java并发工具包java.util.concurrent是Java平台在Java 5版本中引入的一组新的并发编程类库,旨在帮助Java开发者更容易地实现复杂的并发程序。这一包的出现,极大地简化了开发者在处理线程和数据同步时所遇到的难题,...
### Java.util.concurrent.Synchronizer框架详解 #### 一、引言与背景 随着Java技术的发展,多线程编程成为了一项重要的技术需求。为了更好地支持并发编程,Java平台在J2SE 1.5版本中引入了`java.util.concurrent`...
本文将详细探讨Atlassian发布的`atlassian-util-concurrent-0.0.12.jar`库,这是一个专门针对并发处理的工具集,旨在简化Java开发中的多线程操作。 `atlassian-util-concurrent-0.0.12.jar.zip`是这个库的压缩文件...
Synchronized和java.util.concurrent.locks.Lock都是Java中用于实现线程同步的关键字和接口,它们的主要目标是保证多线程环境下的数据一致性与并发安全。然而,两者在使用方式、控制粒度以及灵活性方面存在显著差异...
这个包是Java多线程编程的核心,提供了高效、安全的并发工具,帮助开发者编写更加并发友好的程序。 在Java并发编程中,`java.util.concurrent`(简称JUC)提供了丰富的类和接口,如Executor框架、线程池、并发集合...
文档明确指出,Doug Lea为J2SE 5.0引入的java.util.concurrent包提供了一套精巧的同步器框架,这套框架极大地简化了并发控制的实现,并且在多个领域提供了高效的同步原语,如锁、条件变量、信号量、事件标志等。...
此外,Java还提供了并发工具类,如`java.util.concurrent`包下的`ExecutorService`、`Semaphore`、`CountDownLatch`等,这些工具可以帮助开发者更高效地管理和协调线程,提高并发性能。 总之,Java的多线程和并发...
`java.util.concurrent` 包中的这些同步工具为开发者提供了强大的并发控制手段,使得多线程编程变得更加容易管理。通过合理利用这些工具,开发者可以在保证程序正确性的同时,提高程序的性能和响应速度。无论是简单...
Java并发包(java.concurrent)是Java平台中处理多线程编程的核心工具包,它提供了丰富的类和接口,使得开发者能够高效、安全地编写多线程程序。这个包的设计目标是提高并发性能,减少同步代码的复杂性,并提供高级...
为避免多线程环境下的数据竞争,Java提供了多种同步机制,如`synchronized`关键字、`wait()`, `notify()`和`notifyAll()`方法、`Lock`接口(如`ReentrantLock`)以及`java.util.concurrent`包中的工具类。...
Java.util.concurrent(JUC)是Java平台中的一个核心包,专门用于处理多线程并发问题。这个包包含了大量的工具类和接口,极大地简化了并发编程的复杂性,提高了程序的性能和可伸缩性。本测试源文件主要是针对JUC并发...
1. java.util.concurrent包下的工具类,如CountDownLatch、CyclicBarrier、Semaphore等,用于协调多个线程之间的操作。 以上内容只是《Java多线程编程核心技术》教程中的一部分核心知识点,实际学习中还需要结合...
此外,Java 5引入了`java.util.concurrent`包,包含如`ExecutorService`, `Future`, `Callable`, `Semaphore`, `CyclicBarrier`, `CountDownLatch`等高级并发工具类,这些工具极大地简化了多线程编程。 在并发编程...