10.2 Using Thread-Safe Collections
If you've ever used Java's collection
classes in an environment with lots
of threads, you know that Sun's nod to threading and collections is a bit
heavy-handed. You can either use HashMap, List implementations, and
Set implementations, which aren't thread-safe, and deal with threading
on your own, or you can use Hashtable or Vector, which has
synchronized methods all over the place. Tiger has added a number of
thread-safe collections, many of which perform even better than
Hashtable and Vector when used correctly. While these classes are hardly magic bullets, they offer more variety, and that's always a good
thing.NOTEIt's still beyond me why the read methods on Hashtable are synchronized.
10.2.1 How do I do that?
All of Java's new concurrency support for collections is tucked away in
java.util.concurrent. And, thankfully, the classes that mirror existing
collections serve as drop-in replacements for their non-threadsafe counterparts.
10.2.1.1 ConcurrentHashMap
The first, and probably most
valuable, collection to look at is java.util.concurrent.ConcurrentHashMap. This class makes the obvious first nod
towards concurrence by not synchronizing any of its read methods. That takes care of a lot of locking and threading issues right off the bat. Even
more importantly, ConcurrentHashMap segments its internal hashtable, so
you can write to one segment while another thread writes to another (in
addition to reads always being allowed). In terms of use, it is identical to HashMap, so you can add the following import:
Now all you have to do is search and replace on "HashMap," and you're
import java.util.concurrent.ConcurrentHashMap;
all set. I won't bore you with the details...you should get the idea. Just
make the change, and your code gets all the benefits of concurrent reads,
and even concurrent writes most of the time.
10.2.1.2 CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArrayList is
a thread-aware version
of List, and particular (of course) ArrayList. This is a great solution for arrays that are updated infrequently, but are read very often. It disposes
of synchronization, allowing any number of concurrent reads. For
writing, it actually creates a new copy of the underlying array, and then
assigns that new copy (with changes) back to the underlying copy.
10.2.1.3 CopyOnWriteArraySet
java.util.concurrent.CopyOnWriteArraySet works
just like
CopyOnWriteArrayList, and the same functionality applies. You get
concurrent reading, and pay a fairly minimal performance cost, as long
as you're reading a lot more than you are writing.
10.2.2 What just happened?
ConcurrentHashMap's magic is all in the segmentation of its internal
hashtable. By default, there are 16 segments in this hashmap, and any
operation on one segment has no effect on the othersincluding threading concerns. So you could, theoretically, have 16 threads operating on
16 different segments, all at the same time. If you have specialized needs
for segmentation, you can specify the estimated threads that will write to
the object:
The first parameter is the initial size, common to normal HashMap implementations. Next comes the load factor, and then the concurrency level.
Map map = new ConcurrentHashMap(2000, 25, 25);
This isn't specifically named as the number of segments, but instead as
the number of threads you expect to be performing concurrent updates.
The implementation is then free to perform segmentation and internal
sizing based upon that value.As for the CopyOnWrite collections, you're basically getting around concurrency issues altogether. The reading part is easylet any thread read
from the collection anytime. Writing is a little trickier, and it's here where
the downside comes into play. These collections create entirely new lists
(or sets) on update, make the changes requested, and then the modified
list (or set) is assigned to the instance. This is a pretty clumsy operation if
you're doing lots of writes, but for an occasional write compared to a ton
of reads, it works great, and you get a very fast, thread-aware (albeit not
thread-safe) collection.
10.2.3 What about...
...the other classes and interfaces in java.util.concurrent? Many of
these classes are covered in later labs, such as "Using Blocking Queues"
and "Scheduling Tasks". But, there are some additional collection-analogs
like those discussed here, such as ConcurrentLinkedQueue. Once you
understand the basics presented here, you can figure out how these
extra classes work with a quick glance at the Javadoc. Remember, most
of these are drop-in substitutions for non-concurrent collections, so your
learning curve should be next to nothing.You also might be wondering about how these new classes work with
iterators. Any Iterator instance obtained from CopyOnWriteArrayList and CopyOnWriteArraySet reflects the contents of the list or set when it was obtained. This means that you are essentially getting a snapshot of the list or set, rather than a dynamic version. Depending on your application, this can be absolutely great, or incredibly difficult! In general, use
your iterators and ditch them, obtaining a new instance (through
iterator( )) if you need it again later. This will minimize the possibility
of using stale data in your program logic.