Taming Tiger: Concurrent collections

June 16, 2004

Doug Lea originally written util.concurrent package into a JSR-166, then it is a Tiger version of the J2SE platform. The new library is a set of utilities commonly needed in concurrent programs. If you are interested in multi-threaded for optimized access to the collection, then you're in the right place. Share your thoughts on this article with the author John Zukowski and other readers in the article corresponding discussion forum. (You can also click the top or bottom of the discussion to access the forum).

In the early stages of Java programming, a professor located in the city of Oswego State University (SUNY) New York decided to create a simple library to help developers build better handle multithreaded applications situations. This is not to say that with the existing library can not be achieved, but as with standard network libraries, use after commissioning, the trusted library easier to deal with their own multi-threading. With the help of a Addision-Wesley books, the library has become more and more popular. Finally, the author Doug Lea decided to try to make it a standard part of the Java platform - JSR-166. This library eventually turned into a Tiger version of the java.util.concurrentpackage. In this new Taming Tiger tip we will explore Collection Framework in the new Queueinterfaces, concurrent and non-concurrent implementation of this interface, concurrent Mapimplementations and dedicated to read much more than write complicated by this situation Listand Setachieve.

Introduction Queue Interface

java.utilPackage to provide a new set of basic interfaces: java.util.Queue. Although certainly can be added and deleted in the corresponding ends of which will java.util.Listbe treated as a queue, but this new Queueinterface provides support for adding, deleting and more ways to check that the collection, as follows:

public boolean offer(Object element)
public Object remove()
public Object poll()
public Object element()
public Object peek()

Basically, a queue is a first in first out (FIFO) data structure. Some queue is limited in size, so if you want to add a new item in a queue is full, the extra items will be rejected. At this new offermethod we can play a role. It is not a calling add()method throws an unchecked exception, just get a offer()false return. remove()And poll()methods are deleted first element (head) from the queue. remove()The behavior Collectionversion of the interface is similar, but the new poll()method is not throwing an exception when called with an empty set, just return null. So the new method is more suitable for abnormal conditions are likely to occur. After the two methods element()and peek()used to query the head of the queue elements. And remove()methods similar to, the queue is empty, element()throws an exception, and peek()returns null.





Back to top


Use basic queue

There are two groups in Tiger Queuerealized: to achieve a new BlockingQueueinterface and does not implement this interface. I will first analyze those unfulfilled.

In the simplest case, the original and some java.util.LinkedListimplementations have been transformed into not only implement java.util.Listthe interface, but also to achieve java.util.Queuethe interface. You may be set as any one of both of these. Listing 1 shows the LinkedListas Queuea way to use:


Listing 1. Using Queue Implementation
  Queue queue = new LinkedList();
  queue.offer("One");
  queue.offer("Two");
  queue.offer("Three");
  queue.offer("Four");
  // Head of queue should be One
  System.out.println("Head of queue is: " + queue.poll());

And then a bit more complex is the new java.util.AbstractQueueclass. This class works similar java.util.AbstractListand java.util.AbstractSetclasses. When you create a custom collection, you do not have to implement the entire interface, just inherit the abstract implementation and fill in the details. Use AbstractQueuethe time to be a method offer(), poll()and peek()provide an implementation. Like add()and addAll()such methods modified to use offer()while clear()and remove()use poll(). Finally, element()use peek(). Of course, we can provide optimized implementations of these methods in a subclass, but is not required to do so. Also, you do not have to create your own subclasses, you can use several built-in implementations, two of which are not blocking queue: PriorityQueueand ConcurrentLinkedQueue.

PriorityQueueAnd ConcurrentLinkedQueueclass added in two specific collection implementation in Collection Framework. PriorityQueueClass essentially maintains an ordered list. Was added to Queuethe elements according to their natural ordering (which by java.util.Comparableor passed to the constructor implemented in accordance) java.util.Comparatorimplemented to locate. In Listing 2 LinkedListchanged PriorityQueuewill be printed instead Four One, because alphabetical - native sequence string - Four first. ConcurrentLinkedQueueIt is based on linked nodes, thread-safe queue. Concurrent access synchronization is not required. Because it adds an element at the tail of the queue and delete them from the head, so just need to know the size of the queue, ConcurrentLinkedQueueshare access to a common set of can work very well. Collect information about the size of the queue will be very slow, you need to traverse the queue.





Back to top


Use blocking queue

The new java.util.concurrentconcrete collection classes available in the package is added in the Collection Framework BlockingQueueinterfaces and class five blocking queue. If the queue is not familiar with the concept of blocking, which is essentially a FIFO data structure with a bit of tweaking. Not immediately add or remove elements from the queue, the thread perform the operation block until there is space available or elements. BlockingQueueJavadoc interface gives the basic usage blocking queue, as shown in Listing 2. Producers of put()operation will be blocked when there is no space available, and consumers of take()action there is nothing in the queue obstruction.


Listing 2. Using BlockingQueue
 class Producer implements Runnable {
   private final BlockingQueue queue;
   Producer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while(true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   Object produce() { ... }
 }
 class Consumer implements Runnable {
   private final BlockingQueue queue;
   Consumer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while(true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   void consume(Object x) { ... }
 }
 class Setup {
   void main() {
     BlockingQueue q = new SomeQueueImplementation();
     Producer p = new Producer(q);
     Consumer c1 = new Consumer(q);
     Consumer c2 = new Consumer(q);
     new Thread(p).start();
     new Thread(c1).start();
     new Thread(c2).start();
   }
 }

Have five different queues provided:

  • ArrayBlockingQueue : Backed by an array of a bounded queue.
  • LinkedBlockingQueue : A link from the node supports an optional bounded queue.
  • PriorityBlockingQueue : A stack supported by the priority of unbounded priority queue.
  • DelayQueue : A stack supported by a priority-based scheduling queue time.
  • SynchronousQueue: Use a BlockingQueuesimple aggregation (rendezvous) interface mechanism.

The first two classes ArrayBlockingQueue, and LinkedBlockingQueuenearly identical, differing only in terms of backing store, LinkedBlockingQueuedo not always have the capacity limit. No size limits LinkedBlockingQueueclass when adding an element will never wait for a blocking queue (in which there are at least Integer.MAX_VALUEbefore the elements do not).

PriorityBlockingQueueIs unbounded queue having capacity, containing elements which uses Comparablethe sort order in order to maintain the logical elements. It can be seen as a TreeSetpossible alternative. For example, the string is added in the queue One, Two, Three and Four Four lead to be taken out first. For the element is not the native sequence, it may be provided as a constructor Comparator. But to PriorityBlockingQueuehave a skill. From iterator()returned Iteratorneed not return the element instance priority order. If you must traverse all the elements in priority order, then let them through toArray()methods and sort them yourself, like Arrays.sort(pq.toArray()).

The new DelayQueueimplementation may be a one of the most interesting (and most complicated). Element added to the queue must implement a new Delayedinterface (only one way - long getDelay(java.util.concurrent.TimeUnit unit)). Because the size of the queue is no limit, so that the addition can return immediately, but before the delay time elapses, the element can not be removed from the queue. If a plurality of delay elements is completed, the first fail / fails the longest element first removed. It not actually sounds so complicated. Listing 3 demonstrates this using a new set of blocking queue:


Listing 3. Using DelayQueue achieve
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;
    }
  }
}

The first example is an internal class NanoDelay, it will be suspended substantially any given nanosecond (Nanosecond) number, where the use of Systemthe new nanoTime()method. Then the main()method simply NanoDelayobjects into the queue and taking them out again. If you want to queue item to do some other things, you need to Delayedjoin a method to achieve object, and call this new method after being removed from the queue. (Please expand freely NanoDelayto test join other way to do something interesting.) Display time between the two calls deviation of the elements removed from the queue. If the time difference is negative, it can be seen as a mistake, because never at the end of the delay time, get an item from a queue in an earlier trigger time.

SynchronousQueueClass is the simplest. It has no internal capacity. It's like the hand between the threads handed the phone system. Add an element to the producer in the queue will wait for another thread of consumers. When this occurs the consumer, in this element is transmitted directly between consumers and producers, never added to the blocking queue.





Back to top


使用 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() )调用。对于新的 CopyOnWriteArrayListCopyOnWriteArraySet 类,所有可变的(mutable)操作都首先取得后台数组的副本,对副本进行更改,然后替换副本。这种做法保证了在遍历自身更改的集合时,永远不会抛出 ConcurrentModificationException 。遍历集合会用原来的集合完成,而在以后的操作中使用更新后的集合。

这些新的集合, CopyOnWriteArrayListCopyOnWriteArraySet ,最适合于读操作通常大大超过写操作的情况。一个最常提到的例子是使用监听器列表。已经说过,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());
    }
  }
}

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





回页首


结束语

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






回页首


下载

名字 大小 下载方法
j-tiger06164-source.zip HTTP
关于下载方法的信息


参考资料

转载于:https://www.cnblogs.com/licheng/archive/2008/09/23/1297203.html

Guess you like

Origin blog.csdn.net/weixin_34117211/article/details/92631717