星期三, 三月 10, 2004

编写Java Thread-safe 需要了解的最基本的概念.

thread safe code

Thread-safe code is code that will work even if many Threads
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.