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:

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:

  1. Something (another thread, or a timer) grants this thread a "notification" on object x; then,
  2. the lock on x must be freed by the thread that granted the notification, or subsequent owners, and then be "won" (enabling the restart after the wait()) back again (competing against all other threads in the pool who want x'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!