This article describes how to do concurrent programming with Java and the Java 5.0 concurrency collection. Covers threads, the executor framework (thread pools), futures, callables, deadlocks and the fork-join framework.
Discussion of two more architectural solutions to threading problems: a synchronous dispatcher (or 'reactor') and an asynchronous dispatcher (or 'active object').
A condition variable adds to wait the ability to not wait when the condition you're waiting for has already taken place; and a counting semaphore lets you control a pool of resources without sucking up machine cycles in polling loops.