Listing 8. PhaserDemo.java
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Phaser;
public class PhaserDemo
{
public static void main(String[] args)
{
List<Runnable> tasks = new ArrayList<>();
tasks.add(new Runnable()
{
@Override
public void run()
{
System.out.printf("%s running at %d%n",
Thread.currentThread().getName(),
System.currentTimeMillis());
}
});
tasks.add(new Runnable()
{
@Override
public void run()
{
System.out.printf("%s running at %d%n",
Thread.currentThread().getName(),
System.currentTimeMillis());
}
});
runTasks(tasks);
}
static void runTasks(List<Runnable> tasks)
{
final Phaser phaser = new Phaser(1); // "1" to register self
// create and start threads
for (final Runnable task: tasks)
{
phaser.register();
new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(50+(int)(Math.random()*300));
}
catch (InterruptedException ie)
{
System.out.println("interrupted thread");
}
phaser.arriveAndAwaitAdvance(); // await all creation
task.run();
}
}.start();
}
// allow threads to start and deregister self
phaser.arriveAndDeregister();
}
}
Listing 8 is based on the first code example in Phaser
's Javadoc. This example shows how to use Phaser
instead of CountDownLatch
to control a one-shot action serving a variable number of threads.
The application creates a pair of runnable tasks that each report the time (in milliseconds relative to the Unix epoch) at which its starts to run. Compile and run this application, and you should observe output that's similar to the following:
Thread-0 running at 1366315297635
Thread-1 running at 1366315297635
As you would expect from countdown latch behavior, both threads start running at (in this case) the same time even though a thread may have been delayed by as much as 349 milliseconds thanks to the presence of Thread.sleep()
.
Comment out phaser.arriveAndAwaitAdvance(); // await all creation
and you should now observe the threads starting at radically different times, as illustrated below:
Thread-1 running at 1366315428871
Thread-0 running at 1366315429100
Concurrent collections
The Java Collections framework provides interfaces and classes in the java.util
package that facilitate working with collections of objects. Interfaces include List
, Map
, and Set
. Classes include ArrayList
, Vector
, Hashtable
, HashMap
, and TreeSet
.
Collections classes such as Vector
and Hashtable
are thread-safe. You can make other classes (like ArrayList
) thread-safe by using synchronized wrapper factory methods such as Collections.synchronizedMap()
, Collections.synchronizedList()
, and Collections.synchronizedSet()
.
There are a couple of problems with the thread-safe collections:
- Code that iterates over a collection that might be modified by another thread during the iteration requires a lock to avoid a thrown
java.util.ConcurrentModificationException
. This requirement is necessary because Collections framework classes return fail-fast iterators, which are iterators that throwConcurrentModificationException
when a collection is modified during iteration. Fail-fast iterators are often an inconvenience to concurrent applications. - Performance often suffers when these synchronized collections are accessed frequently from multiple threads; this is a performance problem that impacts an application's scalability.
The Java Concurrency Utilities framework overcomes these problems by introducing performant and highly-scalable collections-oriented types, which are part of java.util.concurrent
. These collections-oriented classes return weakly consistent iterators, which have the following properties:
- When an element is removed after iteration starts, but hasn't yet been returned via the iterator's
next()
method, it won't be returned. - When an element is added after iteration starts, it may or may not be returned.
- Regardless of changes to the collection, no element is returned more than once in an iteration.
The following list summarizes the Java Concurrency Utilities framework's collection-oriented types:
BlockingDeque<E>
: This interface extendsBlockingQueue
andjava.util.Deque
to describe a double-ended queue with additional support for blocking operations that wait for the deque to become non-empty when retrieving an element, and wait for space to become available in the deque when storing an element.BlockingQueue<E>
: This interface extendsjava.util.Queue
to describe a queue with additional support for operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.ConcurrentMap<K, V>
: This interface extendsjava.util.Map
to describe a map with additional atomicputIfAbsent
,remove
, andreplace
methods.ConcurrentNavigableMap<K, V>
: This interface extendsConcurrentMap
andjava.util.NavigableMap
to describe a concurrent map with navigable operations.TransferQueue<E>
: This interface extendsBlockingQueue
to describe a blocking queue in which producers may wait for consumers to receive elements.ArrayBlockingQueue<E>
: This class describes a bounded blocking queue backed by an array.ConcurrentHashMap<K, V>
: This class describes a hash table supporting full concurrency of retrievals and adjustable expected concurrency for updates.ConcurrentLinkedDeque<E>
: This class describes an unbounded thread-safe deque based on linked nodes.ConcurrentLinkedQueue<E>
: This class describes an unbounded thread-safe queue based on linked nodes.ConcurrentSkipListMap<K, V>
: This class describes a scalable concurrentConcurrentNavigableMap
implementation.ConcurrentSkipListSet<E>
: This class describes a scalable concurrentjava.util.NavigableSet
implementation based on aConcurrentSkipListMap
.CopyOnWriteArrayList<E>
: This class describes a thread-safe variant ofArrayList
in which all mutative operations (e.g., add and set) are implemented by making a fresh copy of the underlying array whenever an element is added or removed. However, in-progress iterations continue to work on the previous copy (when the iterator was created). Although there's some cost to copying the array, this cost is acceptable in situations where there are many more iterations than modifications.CopyOnWriteArraySet<E>
: This class describes aSet
that uses an internalCopyOnWriteArrayList
for all of its operations.DelayQueue<E extends Delayed>
: This class describes an unbounded blocking queue ofjava.util.concurrent.Delayed
elements, in which an element can only be taken when its delay has expired. (Delayed
is an interface for marking objects that should be acted upon after a given delay.)LinkedBlockingDeque<E>
: This class describes an optionally-bounded blocking deque based on linked nodes.LinkedBlockingQueue<E>
: This class describes an optionally-bounded blocking queue based on linked nodes.LinkedTransferQueue<E>
: This class describes an unbounded transfer queue based on linked nodes.PriorityBlockingQueue<E>
: This class describes an unbounded blocking queue that uses the same ordering rules asjava.util.PriorityQueue
and supplies blocking retrieval operations.SynchronousQueue<E>
: This class describes a blocking queue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa.
Working with Concurrent Collections
For an example of what you can do with Concurrent Collections, consider CopyOnWriteArrayList
, as demonstrated in Listing 9.
Listing 9. CopyOnWriteArrayListDemo.java
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListDemo
{
public static void main(String[] args)
{
List<String> empList = new ArrayList<>();
empList.add("John Doe");
empList.add("Jane Doe");
empList.add("Rita Smith");
Iterator<String> empIter = empList.iterator();
while (empIter.hasNext())
try
{
System.out.println(empIter.next());
if (!empList.contains("Tom Smith"))
empList.add("Tom Smith");
}
catch (ConcurrentModificationException cme)
{
System.err.println("attempt to modify list during iteration");
break;
}
List<String> empList2 = new CopyOnWriteArrayList<>();
empList2.add("John Doe");
empList2.add("Jane Doe");
empList2.add("Rita Smith");
empIter = empList2.iterator();
while (empIter.hasNext())
{
System.out.println(empIter.next());
if (!empList2.contains("Tom Smith"))
empList2.add("Tom Smith");
}
}
}
Listing 9 contrasts CopyOnWriteArrayListDemo
with ArrayList
from a ConcurrentModificationException
perspective. During each iteration, an attempt is made to add a new employee name to the list. The ArrayList
iteration fails with this exception, whereas the CopyOnWriteArrayList
iteration ignores the addition.
If you compile and run this application you should see the following output:
John Doe
attempt to modify list during iteration
John Doe
Jane Doe
Rita Smith
In conclusion
The Java Concurrency Utilities framework offers a high-level alternative to Java's low-level threading capabilities. This library's thread-safe and high-performant types were designed to be used as the building blocks in concurrent classes and applications.
The Java Concurrency Utilities framework is organized into several smaller frameworks, with types stored in java.util.concurrent
and two subpackages, java.util.concurrent.atomic
and java.util.concurrent.locks
.
In this article, I introduced three of these frameworks: the Executor framework, synchronizers, and the Java Concurrent Collections. You can learn more about them by exploring the exercises in this article's source code file. In Part 2 we'll explore locks, atomic variables, Fork/Join, and more.
Learn more about this topic
- Download the source code for this article.
- Java 101: The next generation: It's time for a change (Jeff Friesen, April 2013) introduces the new Java Date and Time API.
Using the Java Concurrency Utilities -- more tutorials on JavaWorld:
- Modern threading for not-quite-beginners (Cameron Laird, JavaWorld, January 2013): Get an overview of callable and runnable, learn more about synchronized blocks, and find out how you can use
java.util.concurrent
to work around deadlock and similar threading pitfalls. - Multicore processing for client-side Java applications (Kirill Grouchnikov, JavaWorld, September 2007): Get a hands-on introduction to collection sorting using the
CountDownLatch
andExecutors.newFixedThreadPool
concurrency utilities. - Java concurrency with thread gates (Obi Ezechukwu, JavaWorld, March 2009): See the Java concurrency utilities at work in a realistic implementation of the Thread Gates concurrency pattern.
- Hyper-threaded Java (Randall Scarberry, JavaWorld, November 2006): See for yourself how two
java.util.concurrent
classes were used to optimize thread use for faster performance in a real-world application. - Java Tip 144: When to use ForkJoinPool vs ExecutorService (Madalin Ilie, JavaWorld, October 2011): Demonstrates the performance impact of replacing the standard
ExecutorService
class withForkJoinPool
in a web crawler.
Popular articles in the Java 101 series
- Learn Java from the ground up (Jacob Weintraub, March 2000)
- Object-oriented language basics (Jeff Friesen, April through October 2001)
- Trash talk (two parts) (Jeff Friesen, December 2001, January 2002)
- Understanding Java threads (a four-part series) (Jeff Friesen, May through August 2002
This story, "Java 101: Java concurrency without the pain, Part 1" was originally published by JavaWorld.