Notes on Java Threads
After you read a proper Java tutorial on threads (why you want
them, what they're good for, etc.), you might be slightly confused
about issues like the difference between notify() and
notifyAll(), so this web page is an attempt to help you
understand a few particular details... but, be sure to read the
other stuff first, as this is not a tutorial! And, as
always, if you see a mistake, please email me so I can fix it!
There are interesting new features since JDK 1.5 for managing concurrency, and these are certainly well worth a look. Indeed, to quote Sun, "in short, using the Concurrency Utilities to implement a concurrent application can help you make your program clearer, shorter, faster, more reliable, more scalable, easier to write, easier to read, and easier to maintain" [than directly using synchronized]. Their claims are probably true, but I have not tried their libraries yet, given that I rarely program Java*
Big Picture
A thread is an entity that executes your programs. One can create many threads to execute the same program at the same time, or different pieces of the same program at the same time. If two threads are executing methods that belong to a particular object, they both read/modify the same exact class and instance variables! So, one thread can change things "under the nose" of the other, resulting in extreme confusion (and defective programs).
To prevent two threads trying to modify a particular object at the same time, "locks" are provided to grant exclusive access an object only to one thread at a time; other threads who try to get the lock to access the object will "block" (pause) until they are granted the lock, which can only happen after the current thread with the lock gives it up. (Only one thread holds a lock, and the other threads block if they're trying to get that same lock.)
A thread can be actively executing your program, or it can be blocked, which means that it has been "suspended." Suspended threads reside in a pool, doing nothing, unless the Virtual Machine decides to grant them the lock that they need and resume their operation.
Each object behaves "as if" it has a unique lock, where any thread that wants to can "grab" the lock from the object, although threads cannot "steal" acquired locks from each other. Instead, a thread has to give a lock back to the object from whence it came, so another thread can take it, as needed. Threaded programs are carefully written to be collaborative, so the intent is that the threads play well together, and share their locks as needed, promptly relinquishing them (if possible)!
So, while there can (and should) be many threads, at most one thread can hold ("own") the lock of a given object. A lock is not held a given number of times, etc.; a thread either owns a lock, or it does not.
Most objects do not have their lock held by any thread, as no thread cares to do so. An object's lock doesn't do anything by itself, or (directly) impact its associated object in any way; locks exist only for threads to pass around to each other as a means to start/stop themselves appropriately (refer to your [other] tutorials to learn of the benefits of threading).
There is only one way for a thread to to get the lock of an object,
and that is through execution of the synchronized
keyword, which works as follows:
synchronized(x)// grab x's lock, or block (pause),
// until we grab said lock
{
// then, execute the body:
...
// give the lock back (unless nested...).
}.
The code fragment (above) says, "grab the lock of object
x, and only when you have it, execute the
statement's body; finally, release the lock." Note
synchronized(x) {synchronized(x) {...};
A;}
still holds x's lock at point A,
as you would expect.
In passing, if the body is left by abrupt mechanisms (return,
throwing exceptions, etc.), the lock is still relinquished (unless
another enclosing synchronized happens to keep it for the
thread).
While there is a synchronized keyword (with no
arguments) that can be used in the definition of a method, it is
merely syntactic sugar for
synchronized(this) {...},
so we ignore it as being purely cosmetic.
Becoming anthropomorphic for a moment: If you are a thread, and you
hit a synchronized statement, one of three things can
happen to you:
- You already have the lock (as you're within nested
synchronizedstatements, and you took that particular lock earlier): In this case, you just execute the body of the statement. - No thread has the lock: You are granted the lock and start to execute the body. When control flow leaves the body, you relinquish the lock you picked up.
- Another thread has the lock: You block on the
synchronizedstatement (lose consciousness!), and join a pool of other threads also blocked on the same lock (if there are any), each making no forward progress. If the one other thread with the lock decides to give it up, one lucky blocked random thread waiting for said lock in the pool will be restarted by the VM (Virtual Machine) and enter itssynchronizedstatement's body, to execute as normal, holding the lock. A thread has no concept that it's being blocked; it sees itself going into thesynchronizedstatement, and time can stop as far as it's concerned... sometimes, for an eternity, in the sense it will never wake up! The blocked thread can be thought of as being in "suspended animation."
As outlined above, once granted a lock, your thread will hold said
lock for the entire duration of the synchronized
statement. The normal way to "release" a thread is to naturally
finish executing the synchronized body. However, it
turns out to be extremely useful to stop the thread in the middle of
the synchronized body, give up the lock, and wait for
"resuscitation" from another thread, which is done by means of two
interesting messages that can be sent to x,
wait() and
notify()/notifyAll().
Asking for Sleep, then Resuscitation: wait()
The wait() statement only happens one way:
synchronized(x) {... x.wait(); ...}.
The wait command says: Stop this thread's execution right "after"
the x.wait() message. Surrender the lock on
x, so a different blocked thread waiting on the
lock can be restarted (if one exists). Then, the thread that executed
the wait() will sleep forever unless two things
happen, in order:
- Something (another thread, or a timer) grants this thread a
"notification" on object
x;then, - the lock on
xmust be freed by the thread that granted the notification, or subsequent owners, and then be "won" (enabling the restart after thewait()) back again (competing against all other threads in the pool who wantx's lock).
(Any thread holding the lock of x can send
notifications as often as it wishes to other blocked threads
waiting on x, as you will see
shortly; notifications are not "conserved" items like locks.)
There is a variant of wait where you provide an
argument, some number of milliseconds, and after that time the blocked
thread is granted a "notification" on object x,
which meets requirement #1, so the thread is now eligible to be
granted the lock on x (along with certain other
threads in the pool) and resume execution should the lock on
x become free.
If a thread h sends a wait()
message to an object x (where
h must hold x's lock),
h is depending on another thread to ask for
the lock of x, send x a
notify() message, and then relinquish
x's lock... otherwise, the
h's wait() is eternal! Furthermore,
if the notification is granted to another thread, which in turn does
not provide a new notification for h,
h will still never wake up (again)! You
must trust your fellow threads to use wait.
However, if the wait is given a timeout (numerical
argument), the notification is guaranteed to be sent after the
required delay, and the thread can then be granted the lock later and
be resumed. This is like saying, "I'm going to sleep now, and if
another thread does not make provisions to explicitly resuscitate me
by a particular time, grant me notification so I can be granted the
lock if it should become free so I can possibly regain
consciousness."
Preparing for Resuscitation: notify() and
notifyAll()
synchronized(x) {... x.notify(); ....}
The above command picks one lucky blocked thread (at
random?) (from the pool) that executed x.wait() and
grants it a "notification" on x, relaxing
condition #1, so said thread is free to compete for the lock when it
is later released by the running thread that executed the
notify() (either by a wait(), or leaving a
synchronized), every time the lock is up for grabs.
synchronized(x) {... x.notifyAll(); ....}
The above code takes every single thread that is waiting on
x and sends it a notification, so that they are
all free to fight for the lock when it becomes available.
How do I create a thread and make it run my code,
XXX?
The correct recipe looks like this:
public class Foo implements Runnable {...};
public void run() {XXX...};
Foo x = new Foo(); // create a Foo instance
Thread t = new Thread(x); // make a thread t
t.start(); // t will start on x.run()
// your current thread is still alive
While it is possible to subclass the Thread class, the
chances are excellent that you don't want to do that.
If you want to see an extremely simple-minded example of how
wait and notify are useful, look at FIFO.java. One thread can write into the FIFO,
and another can read; note that if the reader blocks because there is
no data, it will get restarted by the writer the moment that there is
something to see, and this is the collaborative beauty of threads.
Kleanthes Koniaris, email.
*I prefer Common Lisp, Haskell, O'Caml and Prolog, as they're really fun---try them!