are executing it simultaneously. Writing it is a black art. It is extremely
difficult to debug since you can't reproduce all possible interactions between Threads.
You have to do it by logic. In a computer, something that happens only one in a
billion times must be dealt with because on average it will happen once a second.
To write code that will run stably for weeks takes extreme paranoia.
Here are your tools for writing thread-safe code.
-
Local variables
Every Thread has its own set of local variables
stored in a stack frame. There is no possible way a Thread
can interfere with another Thread's local variables.
You will often see Sun code where at the beginning of a method it fetches
everything into local variables, then computes, then stores everything back into
instance and class variables. The advantage of this approach is local variables
cannot spontaneously change by the actions of other Threads. -
The volatile keyword
It warns the compiler that other Threads may change
an instance or class variable at any time, and that the compiler should not
cache that value in a register, least it be stale. Volatile
does not imply any locking. It simply that the compiler/JVM does not cache volatile
values in registers. On every use, it fetches the value from main shared RAM. On
every assignment it saves the value to main shared RAM. In a multicpu machine
with cache coherency, the value may not actually be stored all the way back to
RAM, but logically it is. Declaring a long or double
volatile also ensures it is fetched and stored
atomically in one indivisible 64 bit chunk, rather than two 32 bit chunks one
after the other as is normal. Volatile is sufficient to ensure even code as
simply as x++ works. Pathologically you could see this
happening:
Thread (a) reads x, thread (a) increments x in a register, thread (b) reads x,
thread (b) increments x in a register, thread (b) saves x, thread (a) saves x.
The result is only one increment instead of two. You may think this highly
improbable, but in the nanoscopic world of the CPU billions of events happen
every second, so that highly "improbable" things happen frequently, or,
even more infuriatingly, only during demos. -
The synchronized keyword
Think of synchronized as temporarily locking an
object to have exclusing access to it for modifications or study. synchronized
marks a block of code or an entire method as critical. Only one Thread
can be in it at once executing. Other Threads
automatically sleep waiting their turn to use it. For a synchronized instance
method, the synchronized code can be run by other Thread
on other objects simultaneously, since the locking is on the object. For a
synchronized class method, the locking in on the class object, which thus limits
to only one Thread executing that code.
Does the synchronized mechanism lock Threads out of
the object in all critical regions or the object in just one critical region? It
is the first. All critical sections locked with the same object are blocked.
Keep in mind that you can use an object other than the one you are working on as
the locking object. This would let you for example allow two groups of critical
code to be executing simultaneously, provided they locked on different objects,
e.g. a dummy inner class locking object. Understand that the objects themselves
are not locked. Other threads can access them with non-sychronised methods, even
when they are locked. It also means you could have critical code acting on
thousands of different objects funnelled to single processing stream by locking
all the critical code on a single lock object. This would not be efficient, but
would be theoretically possible. -
wait, notify and sleep
For inter-Thread communication you can use Thread.wait
and Thread.notify. Thread.wait
goes to sleep until some other Thread wakes it up. Thread.notify
wakes up some other Thread. Thread.sleep
goes to sleep for some specified number of milliseconds, unless some other Thread
wakes it up first. -
Immutable Objects
Immutable objects, (ones without any setter methods, e.g. Integer)
are automatically thread safe. They are read-only. Combine that with final
references, and you don't need to worry all about what other threads are doing.
Instead of modifying fields in an object, and risking it being in an
indeterminate state, you create a new object, and then replace the object as a
whole with the new one is one atomic operation, setting a new reference. -
interrupt
One Thread can interrupt another with Thread.interrupt.
The most common use is to wake up a sleeping Thread
prematurely, or to abort a long i/o. If a Thread is
computing, interrupting it will have no effect until it does a sleep
or wait etc. Interrupting a Thread
will cause it to immediately wake the next time it tries to sleep.
A Thread can even interrupt itself.
Unfortunately you can't use interrupt to abort just
any i/o. It can really only be used for that when using the JDK 1.4 java.nio
I/O. In particular when using a Channel that
implements InterruptibleChannel, which includes
channels for files, pipes and networking. Thread.interrupt
is not guaranteed to wake up any thread blocked in any other I/O operation.
See the warning under Gotchas:Threads on why
a sleeping task may never waken if somebody fiddles with the system clock
setting while your thread is asleep.
start versus run
The easiest way to start a Thread is to implement Runnable
on some class. All you have to do is say implements Runnable
and write a run method that is called when the Thread
forks (starts). However you don't call run
directly.