- Why Concurrent Programming?
- Synchronizers: A Semaphore Example
- Concurrent Locks and TimeUnit
- Queues and Executors
- Conclusion
Concurrent Locks and TimeUnit
Another mechanism for achieving exclusive locking is by means of the Lock interface. A number of classes implement this interface; one such class is java.util.concurrent.locks.ReentrantLock. The Lock interface allows for a flexible locking mechanism, as shown in Listing 2.
Listing 2 The Lock interface.
public class TimeOut { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); try { if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) { System.out.println("Success! Time to get busy"); System.out.println("holdCount = " + lock.getHoldCount()); } else { System.out.println("Deferred success!"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("New holdCount = " + lock.getHoldCount()); } } }
Notice the try-catch-finally block. In the try clause, a call is made to lock.tryLock(). This method provides the ability to acquire the lock if the following conditions are met:
- The lock is not currently held by another thread within the given waiting time (50 milliseconds in this example).
- The current thread has not been interrupted.
These semantics allow for a flexible approach to lock acquisition. Notice the timeout value of 50 milliseconds. If the lock is obtained within the timeout period, the critical code can be executed, and during this time the value of lock.getHoldCount() is 1. This result is easy to understand: One thread has reserved the lock for use.
Remember that when you have a finally statement, you're guaranteed that the code inside it will run. This means that we will always invoke lock.unlock(), thereby releasing the lock.
Concurrent Versus Synchronized Access
Earlier I mentioned the problem of excessive use of synchronized methods. Here's an important question: What's the difference between concurrent and synchronized access? The answer is quite illuminating: A concurrent object is thread-safe, but access to it is not controlled by a single exclusive lock, such as a semaphore. The difference with a synchronized object is that it's governed by a single exclusive lock. For example, an instance of CopyOnWriteArrayList allows any number of readers as well as a writer.
The rule is that a synchronized mechanism is coarse-grained and excludes other threads from gaining access to the protected resource. By contrast, a concurrent mechanism is fine-grained and allows other threads to gain access to the protected resource—albeit in a controlled and possibly limited manner.
Let's take a look at one of the concurrent collection classes.
Concurrent Collections
Iterators of concurrent collections are said to provide weakly consistent rather than fast-fail traversal. This means that a copy of the list is taken at the time an iterator on the collection is created. Therefore, if an addition to the collection is made after the iterator is created, no java.util.ConcurrentModificationException exception is thrown. Listing 3 provides an example.
Listing 3 A concurrent collection.
public class ConcurrentCopyOnWrite { public static void main(String args[]) { List<String> list1 = new CopyOnWriteArrayList<String>(); ArrayList<String> list2 = new ArrayList<String>(); list1.add("First element in concurrent list"); list2.add("First element in classic list"); Iterator<String> itor1 = list1.iterator(); Iterator<String> itor2 = list2.iterator(); list1.add("New element in concurrent list"); list2.add("New element in classic list"); printAll(itor1); printAll(itor2); itor1 = list1.iterator(); printAll(itor1); itor2 = list2.iterator(); printAll(itor2); } private static void printAll(Iterator<?> itor) { try { while (itor.hasNext()) { System.out.println(itor.next()); } } catch (Exception e) { System.out.println("Exception occurred: " + e.toString()); } } }
Listing 3 creates two list structures: a CopyOnWriteArrayList list and a classical ArrayList. Next, we add an element to each list and then create an iterator for both lists. Nothing too special yet. We can then iterate over each list. However, instead of iterating, we add a second element to each list and then call the method printAll(). The output is displayed in Listing 4.
Listing 4 Program output—list iterators.
First element in concurrent list Exception occurred: java.util.ConcurrentModificationException First element in concurrent list New element in concurrent list First element in classic list New element in classic list
The important point to notice in Listing 4 is that the classical ArrayList iteration has thrown an exception, while the CopyOnWriteArrayList iteration has not thrown an exception. Instead of an exception, the iteration of the CopyOnWriteArrayList object has simply produced the original list contents (that is, one entry).
The class CopyOnWriteArrayList is ideal for situations in which you have many readers of a list but very infrequent updates. The reads (list iterations) are guaranteed to produce the state prior to any additions, in a thread-safe manner.
Notice after the exception in Listing 4 that the two lists are fully traversed. This is because we deliberately synchronize the traversal of both lists at the end of Listing 3.