Taming Tiger: Concurrent collections of: John Zukowski

xiaoxiao2021-03-06  86

Tam Tiger: Concurrent Collection

Beyond MAP, Collection, List and Set

John Zukowski

(Jaz@zukowski.net) President, JZ Ventures, Inc. June 2004

At the early stage of Java programming, a professor at Suny, the New York State University (Suny) in Oswego decided to create a simple library to help developers build applications that can better handle multithreading. This is not to say that you cannot be implemented in an existing library, but just like there is a standard network library, it is easier to handle multithreading yourself with a debugged, trusted library. With the help of an associated books of Addision-Wesley, this library becomes 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 finally became the Tiger version of the java.util.concurrent package. In this new tame Tiger tip, we will explore the new Queue interface in Collection Framework. The non-equivalence and concurrent implementation of this interface, concurrent MAP implementation and dedicated to read operations, greatly exceeded the writing of this situation, List and SET achieve.

Introduction Queue Interface Java.util package provides a new basic interface: java.util.queue. Although it is sure to add java.util.list as a queue to the corresponding two-end, this new Queue interface provides more methods for supporting, deleting, and checking a 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 data structure of a first in the first out (FIFO). Some queues have a size limit, so if you want to join a new item in a full queue, you will be rejected. At this time, the new OFFER method can work. It is not a unchecked exception to call the add () method, but just get the false returned by Offer (). The REMOVE () and poll () methods are removed from the queue (head). Remove () behavior is similar to the version of the Collection interface, but the new poll () method does not throw an exception when calling with an empty set, just returning NULL. Therefore, new methods are more suitable for the case where abnormal conditions are prone to. The latter two methods Element () and peek () are used to query elements at the header of the queue. Similar to the remove () method, when the queue is empty, Element () throws an exception, and PEEK () returns NULL.

Using the basic queue in TiGer has two groups of queue implementations: the new BlockingQueue interface is implemented and the interface is not implemented. I will first analyze those who are not implemented.

In the simplest case, the original Java.util.LINKEDLIST implementation has been transformed into a Java.util.List interface, but also implements a java.util.Queue interface. A collection can be regarded as any of the two. Listing 1 Display a way to use LinkedList as Queue:

Listing 1. Using Queue Realization

Queue queue = new linkedlist ();

Queue.offer ("one");

Queue.offer ("two");

Queue.offer ("three");

Queue.offer ("four"); // HEAD of queue shouth be one

System.out.println ("Head of queue is:" queue.poll ());

More complicated is a new Java.util.AbstractRactQueue class. This type of work is similar to the Java.util.AbstractList and Java.util.AbstractSet class. When you create a custom set, you don't need to implement the entire interface, just inherit the abstraction implementation and fill in the details. When using AbstractQueue, it must be implemented for method Offer (), POLL (), and peek (). Methods such as add () and addall () are modified to use offer (), while Clear () and remove () use Poll (). Finally, Element () uses peek (). Of course, you can provide optimization implementation of these methods in subclasses, but don't do this. Moreover, you don't have to create your own subclasses, you can use several built-in implementations, two of which do not block queues: priorityQueue, and ConcurrentLinkedQueue.

The PriorityQueue and the ConcurrentlinkedQueue class are added to the Collection Framework to implement two specific collection implementations. The PriorityQueue class essentially maintains a sequence list. The elements added to Queue are positioned according to their natural sorting (implemented by their java.util.comParable) or by the Java.util.comParator implementation of the constructor. Change the LinkedList in Listing 2 to PriorityQueue will print out of ONE, because the natural order of the alphabetic-string - Four is the first. ConcurrentlinkedQueue is a link-based queue based on link nodes. Concurrent access does not need to synchronize. Because it adds an element to the tail of the queue and deletes them from the head, as long as it does not need to know the size of the queue, ConcURRENTLINKEDQUEUE can work well to public collections. Collecting information about the size of the queue will be slow and need to traverse queues.

Use the blocked queue new java.util.concurrent package to join the BlockingQueue interface and five block queues in the specific set class available in Collection Framework. If you don't get familiar with the blockage concept, it is essentially a FIFO data structure with a distortion. Not immediately adding or deleting elements from the queue, thread performs operational operation, until space or elements are available. The javadoc of the BlockingQueue interface gives the basic usage of blocking queues, as shown in Listing 2. The PUT () operation in the producer will block without space, while the consumer's Take () operation will block when there is no thing in the queue.

Listing 2. Using BlockingQueue

Class producer imports runnable {

PRIVATE FINAL BLOCKINGQUE Queue;

PROCKINGQUE q) {queue = q;}

Public void run () {

Try {

While (true) {queue.put (product ());

} catch (interruptexception ex) {... handle ...}

}

Object product () {...}

}

Class Consumer IMPLEments Runnable {

PRIVATE FINAL BLOCKINGQUE Queue;

CONSUMER (BlockingQueue q) {queue = q;} public void run () {

Try {

While (True) {consume (queue.take ());

} catch (interruptexception ex) {... handle ...}

}

Void Consume (Object X) {...}

}

Class setup {

Void main () {

BlockingQueue q = new somequeueImplementation ();

Producer P = New ProductER (q);

Consumer C1 = New Consumer (q);

Consumer C2 = New Consumer (q);

New thread (p) .start ();

New thread (c1) .start ();

New Thread (C2) .start ();

}

}

The five queues provide different:

ArrayBlockingQueue: A bounded queue supported by arrays. LinkedBlockingQueue: An optional bound query supported by the link node. PriorityBlockingQueue: A unreasonable priority queue supported by priority stacks. DELAYQUEUE: A time-based dispatch queue supported by priority. SynchronousQueue: A simple aggregation mechanism using the BlockingQueue interface.

The first two class ArrayBlockingQueue and LinkedBlockingQueue are almost the same, but they are different in the backup memory. LinkedBlockingQueue does not always have capacity boundaries. LINKEDBLOCKINGQUEUE classes without size boundary never wait for an interpolation queue (at least before it has Integer.max_Value elements).

PriorityBlockingQueue is a queue with unbreranged capacity, which uses the Comparable order sequencing order of the elements to logically sequentially maintained elements. It can be seen as a possibility for Treeset. For example, adding strings in the queue ONE, TWO, Three, and FOUR will result in FOUR to be taken out. For elements without natural sequence, a Compare can be provided for the constructor. But there is a trick to PriorityBlockingQueue. The Iterator instance returned from iTerator () does not need to return elements in priority order. If you must traverse all elements in priority order, let them sort them by the toarray () method, like arrays.sort (pq.toArray ()).

The new DELAYQUEUE implementation may be one of the most interesting (and most complex). Elements joining into the queue must implement a new Delayed interface (only one method - long getdelay (java.util.concurrent.timeUnit unit)). Because the queue size has no boundary, the addition can be returned immediately, but the elements cannot be removed from the queue before the delay time has passed. If multiple elements have completed delay, the earliest failure / failed time is the first to take the first. I actually didn't listen to this complicated. Listing 3 demonstrates the use of this new block queue collection:

Listing 3. Using DELAYQUEUE

Import java.util. *;

Import java.util.concurrent. *;

PUBLIC CLASS DELAY {

/ **

* Delayed Implementation That Actually Delays * /

Static class nanodelay imports 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

IF (i> j) Return 1;

Return 0;

}

Public Boolean Equals (Object Other) {

Return (Nanodelay) .trigger == Trigger;

}

Public Boolean Equals (Nanodelay Other) {

Return (Nanodelay) .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 = (queue.take ());

Long TT = delay.gettriggertime ();

System.out.println ("Trigger Time:" TT);

IF (i! = 0) {

System.out.println ("Delta:" (TT - Last));

}

Last = TT;

}

}

}

This example is first is an internal class Nanodelay, which is substantially a number of nanoseconds, which utilizes the new nanotime () method of System. The main () method is then just putting the Nanodelay object in the queue and takes them again. If you want the queue item to do something else, you need to join the method in the implementation of the Delayed object, and call this new method after taking it from the queue. (Please extend Nanodelay to do some interesting things to add other methods.) Display the time difference between the two calls from the queue. If the time difference is a negative number, it can be considered an error, because it will never be taken from the queue from the queue after the delay time is over.

The SynchronousQueue class is the easiest. It has no internal capacity. It is like a hand hand mechanism between threads. Add an element in the queue to wait for another thread consumers. When this consumers appear, this element is directly transmitted between consumers and producers, never add to the blocking queue. Using ConcURRENTMAP to implement new java.util.concurrent.concurrentMap interface and ConcurrentHashmap implementation can only be added to MAP when the key does not exist, and one element can be removed from the MAP when the key is present and mapped to a particular value.

There is a new PutifabSent () method for adding in MAP. This method is parameter to the value of the key to add to the key in the ConcURrentMap, just like a normal PUT () method, but only the key can be added to the MAP only when the MAP does not contain this button. If the map already contains this button, the existing value of this button will remain. The PUTIFABSENT () method is atomic. If this atomic operation is not called, you need to call the code in the list 4 from the appropriate synchronization block:

Listing 4. Equivalent PUTIFABSENT () code

IF (! map.containskey (key)) {

Return map.put (key, value);

} else {

Return map.get (key);

}

Like the powging () method, the Remove () method after the overload is two parameters - keys and values. When the call is called, this button is removed from the MAP when the key is mapped to the specified value. If you don't match, then this button is not deleted and returns false. If the value matches the current map content of the key, then the key is removed. Listing 5 shows the equivalent source code for this operation:

Listing 5. Equivalent REMOVE () code

IF (Map.get (key) .Equals (value) {

Map.Remove (key);

Return True;

} else {

Return False;

}

Copy-on-write mode is the best description of Copy-On-Write mode using CopyonWriteArraylist and CopyonWriteArraySet, chapter 2.4.4, chapter 2.4.4 (see Resources). In essence, this pattern declares that in order to maintain the consistency snapshot of the object, it is necessary to rely on the immutability to eliminate synchronization required when coordinated different but related properties. For collection, this means that if there is a lot of read (ie, get ()) and iteration, it is not necessary to simultaneously operate to take care of the occasional write (ie the add ()) call. For new CopyonWriteArrayList and CopyonWriteArraySet classes, all variable operations first get a copy of the rear array, change the copy, and then replace the copy. This approach guarantees that when traversing the collection of itself, never throws the ConcurrentModificationException. The traversal collection will be done with the original set, while the updated collection is used in later operations.

These new collections, CopyonWriteArrayList, and CopyonWriteArraySet are most suitable for read operations. One most commonly mentioned example is to use a list of listeners. It has been said that the Swing component has not been changed to the use of new collections. Instead, they continue to use javax.swing.event.eventListenerList to maintain their listener lists. As shown in Listing 6, the use of the collection is exactly the same as their non-Copy-ON-WRITE alternatives. Just create a collection and add or delete an element therein. Even if the object is added to the collection, the original Iterator can also perform, continue to traverse the items in the original collection.

Listing 6. Show a Copy-ON-Write Collection

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 ());

}

}

}

This sample program creates two instances of CopyonWriteArrayList and Arraylist with command line parameters. After getting the Iterator of each instance, an element is added therein. When ArrayList iteration is immediately stopped by a ConcURRentModificationException problem, CopyonWriteArrayList iteration can continue without throwing an exception, because the original collection is changed after Iterator. If this behavior (such as notifying all elements in a set of event listeners) is what you need, it is best to use a Copy-ON-WRITE collection. If you don't use it, use the original and guarantee that it will be processed when an abnormality occurs.

Conclusion There are many important increases in the Tiger version of the J2SE platform. In addition to the changes in language levels, such as general support, this library may be the most important increase because it will be used by the broadest user. Don't ignore other packages added to the platform, like Java Management Extensions (JMX), but most other important libraries are enhanced only for developers with narrow range. But this library is not. These classes are often used in addition to other concurrent utilities for locking and atomic operations. Learn them as soon as possible and use the features they provide.

转载请注明原文地址:https://www.9cbs.com/read-121927.html

New Post(0)