驯服 Tiger: 并发集合

2011年08月16日 23:32 来源:普索网 作者:John Zukowski | 关键词:驯服 tiger 并发 集合 nbsp java
Doug Lea 最初编写的 util.concurrent 包变成了 JSR-166 ,然后又变成了 J2SE 平台的 Tiger 版本。这个新库提供的是并发程序中通常需要的一组实用程序。如果对于优化对集合的多线程访问有兴趣,那么您就找对地方了。请在本文对应的讨论论坛上与作者 John Zukowski 及其他读者分享您对本文的想法。(您也可以单击文章顶部或底部的 讨论来访问论坛)。

 

  新的 DelayQueue 实现可能是其中最有意思(也是最复杂)的一个。加入到队列中的元素必须实现新的 Delayed 接口(只有一个方法 —— long getDelay(java.util.concurrent.TimeUni t unit) )。因为队列的大小没有界限,使得添加可以立即返回,但是在延迟时间过去之前,不能从队列中取出元素。如果多个元素完成了延迟,那么最早失效/失效时间最长的元素将第一个取出。实际上没有听上去这样复杂。清单 3 演示了这种新的阻塞队列集合的使用:

  清单 3. 使用 DelayQueue 实现

  import java.util.*;

  import java.util.concurrent.*;

  public class Delay {

  /**

  * Delayed implementation that actually delays

  */

  static class NanoDelay implements Delayed {

  long trigger;

  NanoDelay(long i) {

  trigger = System.nanoTime() + i;

  }

  public int compareTo(Object y) {

  long i = trigger;

  long j = ((NanoDelay)y).trigger;

  if (i < j) return -1;

  if (i > j) return 1;

  return 0;

  }

  public boolean equals(Object other) {

  return ((NanoDelay)other).trigger == trigger;

  }

  public boolean equals(NanoDelay other) {

  return ((NanoDelay)other).trigger == trigger;

  }

  public long getDelay(TimeUnit unit) {

  long n = trigger - System.nanoTime();

  return unit.convert(n, TimeUnit.NANOSECONDS);

  }

  public long getTriggerTime() {

  return trigger;

  }

  public String toString() {

  return String.valueOf(trigger);

  }

  }

  public static void main(String args[]) throws InterruptedException {

  Random random = new Random();

  DelayQueue queue = new DelayQueue();

  for (int i=0; i < 5; i++) {

  queue.add(new NanoDelay(random.nextInt(1000)));

  }

  long last = 0;

  for (int i=0; i < 5; i++) {

  NanoDelay delay = (NanoDelay)(queue.take());

  long tt = delay.getTriggerTime();

  System.out.println("Trigger time: " + tt);

  if (i != 0) {

  System.out.println("Delta: " + (tt - last));

  }

  last = tt;

  }

  }

  }

  这个例子首先是一个内部类 NanoDelay ,它实质上将暂停给定的任意纳秒(nanosecond)数,这里利用了 System 的新 nanoTime() 方法。然后 main() 方法只是将 NanoDelay 对象放到队列中并再次将它们取出来。如果希望队列项做一些其他事情,就需要在 Delayed 对象的实现中加入方法,并在从队列中取出后调用这个新方法。(请随意扩展 NanoDelay 以试验加入其他方法做一些有趣的事情。)显示从队列中取出元素的两次调用之间的时间差。如果时间差是负数,可以视为一个错误,因为永远不会在延迟时间结束后,在一个更早的触发时间从队列中取得项。

  SynchronousQueue 类是最简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列中。

  使用 ConcurrentMap 实现

  新的 java.util.concurrent.ConcurrentMap 接口和 ConcurrentHashMap 实现只能在键不存在时将元素加入到 map 中,只有在键存在并映射到特定值时才能从 map 中删除一个元素。

  有一个新的 putIfAbsent() 方法用于在 map 中进行添加。这个方法以要添加到 ConcurrentMap 实现中的键的值为参数,就像普通的 put() 方法,但是只有在 map 不包含这个键时,才能将键加入到 map 中。如果 map 已经包含这个键,那么这个键的现有值就会保留。 putIfAbsent() 方法是原子的。如果不调用这个原子操作,就需要从适当的同步块中调用清单 4 中的代码:

  清单 4. 等价的 putIfAbsent() 代码

  if (!map.containsKey(key)) {

  return map.put(key, value);

  } else {

  return map.get(key);

  }

  像 putIfAbsent() 方法一样,重载后的 remove() 方法有两个参数 —— 键和值。在调用时,只有当键映射到指定的值时才从 map 中删除这个键。如果不匹配,那么就不删除这个键,并返回 false。如果值匹配键的当前映射内容,那么就删除这个键。清单 5 显示了这种操作的等价源代码:

  清单 5. 等价的 remove() 代码

  if (map.get(key).equals(value)) {

  map.remove(key);

  return true;

  } else {

  return false;

  }

  使用 CopyOnWriteArrayList 和 CopyOnWriteArraySet

  在 Doug Lea 的 Concurrent Programming in Java一书的第 2 章第 2.4.4 节(请参阅 参考资料)中,对 copy-on-write 模式作了最好的描述。实质上,这个模式声明了,为了维护对象的一致性快照,要依靠不可变性(immutability)来消除在协调读取不同的但是相关的属性时需要的同步。对于集合,这意味着如果有大量的读(即 get() ) 和迭代,不必同步操作以照顾偶尔的写(即 add() )调用。对于新的 CopyOnWriteArrayList 和 CopyOnWriteArraySet 类,所有可变的(mutable)操作都首先取得后台数组的副本,对副本进行更改,然后替换副本。这种做法保证了在遍历自身更改的集合时,永远不会抛出 ConcurrentModificationException 。遍历集合会用原来的集合完成,而在以后的操作中使用更新后的集合。

  这些新的集合, CopyOnWriteArrayList 和 CopyOnWriteArraySet ,最适合于读操作通常大大超过写操作的情况。一个最常提到的例子是使用监听器列表。已经说过,Swing 组件还没有改为使用新的集合。相反,它们继续使用 javax.swing.event.EventListenerList 来维护它们的监听器列表。

  如清单 6 所示,集合的使用与它们的非 copy-on-write 替代物完全一样。只是创建集合并在其中加入或者删除元素。即使对象加入到了集合中,原来的 Iterator 也可以进行,继续遍历原来集合中的项。

  清单 6. 展示一个 copy-on-write 集合

  import java.util.*;

  import java.util.concurrent.*;

  public class CopyOnWrite {

  public static void main(String args[]) {

  List list1 = new CopyOnWriteArrayList(Arrays.asList(args));

  List list2 = new ArrayList(Arrays.asList(args));

  Iterator itor1 = list1.iterator();

  Iterator itor2 = list2.iterator();

  list1.add("New");

  list2.add("New");

  try {

  printAll(itor1);

  } catch (ConcurrentModificationException e) {

  System.err.println("Shouldn't get here");

  }

  try {

  printAll(itor2);

  } catch (ConcurrentModificationException e) {

  System.err.println("Will get here.");

  }

  }

  private static void printAll(Iterator itor) {

  while (itor.hasNext()) {

  System.out.println(itor.next());

  }

  }

  }

  这个示例程序用命令行参数创建 CopyOnWriteArrayList 和 ArrayList 这两个实例。在得到每一个实例的 Iterator 后,分别在其中加入一个元素。当 ArrayList 迭代因一个 ConcurrentModificationException 问题而立即停止时, CopyOnWriteArrayList 迭代可以继续,不会抛出异常,因为原来的集合是在得到 iterator 之后改变的。如果这种行为(比如通知原来一组事件监听器中的所有元素)是您需要的,那么最好使用 copy-on-write 集合。如果不使用的话,就还用原来的,并保证在出现异常时对它进行处理。

  结束语

  在 J2SE 平台的 Tiger 版中有许多重要的增加。除了语言级别的改变,如一般性支持,这个库也许是最重要的增加了,因为它会被最广泛的用户使用。不要忽视加入到平台中的其他包,像 Java Management Extensions (JMX),但是大多数其他重要的库增强只针对范围很窄的开发人员。但是这个库不是。除了用于锁定和原子操作的其他并发实用程序,这些类也会经常使用。尽早学习它们并利用它们所提供的功能。

关键字:驯服 tiger 并发 集合 nbsp java | 来源:普索网 | 责任编辑:系统管理员 | 最后编辑:2011年08月16日 23时32分20秒

热点专题

Copyright©2010 -普索网CMS2.0 All Rights Reserved 版权所有
京ICP备09081852号