Principle
Use the higher-level concurrency utilities
instead of wait and notify for easiness.
Use ConcurrentHashMap in preference to
Collections.synchronizedMap or Hashtable.
Use concurrent collections in preference to
externally synchronized collections.
Three categories of higher-level utilities in
java.util.concurrent
- Executor Framework (Item
68) - Concurrent collections - provide high-
performance concurrent implementations of standard collection interfaces such
as List, Queue, and Map.Since all the implementation of Concurrent
collections manage their own synchronization internally it‘s impossible to
exclude concurrent activity from a concurrent collection; locking it will have
no effect but slow the program.
// Method simulates the behavior of
String.intern. Concurrent canonicalizing map atop ConcurrentMap -
faster!
private static final
ConcurrentMap<String, String> map = new ConcurrentHashMap<String,
String>();
public static String
intern(String s) {
String result =
map.get(s);
if (result == null) {
result =
map.putIfAbsent(s, s);
if (result == null)
result = s;
}
return result;
}
Note
String.intern must use
some sort of weak reference to keep from leaking memory over time.
Blocking operation -
wait until they can be successfully performed.
BlockingQueue (Used
for work queues) extends Queue and adds several methods, including take, which
removes and returns the head element from the queue, waiting if the queue is
empty.
- Synchronizers - Objects that enable
threads to wait for one another.(eg. CountDownLatch, Semaphore,
CyclicBarrier and Exchanger).Countdown latches are single-use barriers that
allow one or more threads to wait for one or more other threads to do
something./**
* Concurrency timer demo for "69 Prefer
concurrency utilities to wait and notify".*/
package com.effectivejava.concurrency;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author Kaibo Hao
*
*/
public class ExecutorManager {
// Simple framework
for timing concurrent executionpublic static long
time(Executor executor, int concurrency,final Runnable
action) throws InterruptedException {final CountDownLatch
ready = new CountDownLatch(concurrency);final CountDownLatch
start = new CountDownLatch(1);final CountDownLatch
done = new CountDownLatch(concurrency);for (int i = 0; i
< concurrency; i++) {executor.execute(new
Runnable() {public void run() {
ready.countDown();
// Tell timer we‘re readytry {
start.await(); //
Wait till peers are readyaction.run();
} catch
(InterruptedException e) {Thread.currentThread().interrupt();
} finally {
done.countDown();
// Tell timer we‘re done}
}
});
}
ready.await(); //
Wait for all workers to be readylong startNanos =
System.nanoTime();start.countDown();
// And they‘re off!done.await(); //
Wait for all workers to finishreturn
System.nanoTime() - startNanos;}
/**
* @param args
*/
public static void
main(String[] args) {try {
Executor executor =
new ThreadPoolExecutor(0, 2, 10,TimeUnit.MILLISECONDS, new
SynchronousQueue<Runnable>());long executedTime =
time(executor, 2, new Runnable() {@Override
public void run() {
System.out.printf("Runing %s%n",
Thread.currentThread());}
});
System.out.printf("%sns %.3fms %.3fs", executedTime,
executedTime /
1000.0, executedTime / 1000000.0);} catch
(InterruptedException e) {e.printStackTrace();
}
}
}
Note
If a worker thread catches an
InterruptedException, it reasserts the interrupt using the idiom
Thread.currentThread().interrupt() and returns from its run method.
Since System.nanoTime is both more accurate and
more precise, and it is not affected by adjustments to the system‘s real-time
clock. For interval timing, always use System.nanoTime in preference to
System.currentTimeMillis.
Always use the wait loop idiom to invoke
the wait method; never invoke it outside of a loop.
// The standard idiom for using the wait
method
synchronized (obj) {
while (<condition
does not hold>)
obj.wait(); //
(Releases lock, and reacquires on wakeup)
... // Perform action
appropriate to condition
}
Reasons a thread might wake up when the
condition does not hold:
? Another thread could have obtained the lock and
changed the guarded state between the time a thread invoked notify and the time
the waiting thread woke.
? Another thread could have invoked notify
accidentally or maliciously when the condition did not hold. Classes expose
themselves to this sort of mischief by waiting on publicly accessible objects.
Any wait contained in a synchronized method of a publicly accessible object is
susceptible to this problem.
? The notifying thread could be overly "generous"
in waking waiting threads. For example, the notifying thread might invoke
notifyAll even if only some of the waiting threads have their condition
satisfied.
? The waiting thread could (rarely) wake up in
the absence of a notify. This is known as a spurious wakeup[Posix, 11.4.3.6.1;
JavaSE6].
Summary
using wait and notify directly is like
programming in "concurrency assembly language," as compared to the higher-level
language provided by java.util.concurrent. There is seldom, if ever, a
reason to use wait and notify in new code.
If you maintain code that uses wait and notify, make sure that it always invokes
wait from within a while loop using the standard idiom.
The notifyAll method should generally be used in preference to notify. If notify
is used, great care must be taken to ensure liveness.
Effective Java 69 Prefer concurrency utilities to wait and
notify