Java 1.5 Tiger A Developers Notebook [Electronic resources]

David Flanagan, Brett McLaughlin

نسخه متنی -صفحه : 131/ 95
نمايش فراداده

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.

NOTE

It'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:

import java.util.concurrent.ConcurrentHashMap;

Now all you have to do is search and replace on "HashMap," and you're 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:

Map map = new ConcurrentHashMap(2000, 25, 25);

The first parameter is the initial size, common to normal HashMap implementations. Next comes the load factor, and then the concurrency level. 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.