Prefer modern libraries for concurrency
Version 1.5 of the JDK included a new package called java.util.concurrent.
It contains modern tools for operations involving threads.
You should usually prefer using this package instead of older, more low-level techniques.
The java.util.concurrent package is fairly large.
The best guide to its contents is likely the excellent book Java Concurrency in Practice, written by Brian Goetz and other experts.
The following is a very brief sketch of the most important elements in java.util.concurrent.
The most important idea is the separation of tasks and the policy for their execution.
A task (an informal term) comes in two flavours:
java.lang.Runnable
- a task which doesn't return a value (like a void
method).
Callable
- a task which can return a value (this is the more flexible form)
For executing tasks, there are 3 important interfaces, which are linked in an inheritance chain.
Starting at the top level, they are:
The Executors
factory class returns implementations of ExecutorService
and ScheduledExecutorService
.
When you need to be informed of the result of a task after it completes, you will likely use these items:
Finally, the following are used to synchronize between threads:
Some example code:
Concurrency uses many specialized terms. Some definitions:
- user thread, daemon thread - in Java, the JRE terminates when all user threads terminate; a single user thread will prevent the JRE from terminating. A daemon thread, on the other hand, will not prevent the JRE from terminating.
- blocking - if a thread pauses execution until a certain condition is met, then it's said to be blocking.
- safety - when nothing unpleasant happens in a multithreaded environment (this is admittedly vague).
- liveness - execution continues, eventually; there may be significant pauses in a thread, but the application never completely hangs.
- deadlock - execution hangs, since a pair of threads each holds (and keeps) a lock that the other thread needs in order to continue.
- race condition - if you are unlucky with the timing of how threads interleave their execution, then bad things will happen.
- re-entrant - if a thread already holds a re-entrant lock, and if it needs to re-acquire the lock, then the re-acquisition always succeeds without blocking. Most locks are re-entrant, including Java's intrinsic locks (see below).
- intrinsic lock - the built-in locks of the Java language. These correspond to uses of the
synchronized
keyword.
- mutex lock - mutually exclusive locks are held by at most one thread at a time. For example, intrinsic locks are mutexes. Read-write locks are not mutexes, since they allow N readers to access the same data at once.
- thread confinement - data that is only accessed by a single thread is always safe, since it's confined to a single thread.
- semaphore - object pools containing a limited number of resources use a semaphore to keep track of how many of the resources are currently in use.
Here's the lifecycle of a task submitted to an Executor
:
- created - the task object has been created, but not yet submitted to an
Executor
.
- submitted - the task has been submitted to an
Executor
, but hasn't been started yet. In this state, the task can always be cancelled.
- started - the task has begun. The task may be cancelled, if the task is responsive to interruption.
- completed - the task has finished. Cancelling a completed task has no effect.
See Also :