Designing For Different Threading Models
The determination of whether the threading model is preemptive or cooperative is up to the implementers of the virtual machine and can vary across different implementations. As a result, Java developers must write programs that work under both models.
Under the preemptive model, as you learned earlier, threads can be interrupted in the middle of any section of code, except for an atomic block of code. Atomic sections are code segments that once started will be finished by the current thread before it is swapped out. In Java programming, assignment to variables smaller than 32 bits is an atomic operation, which excludes variables of types
long (both are 64 bits). As a result, atomic operations do not need synchronization. The use of locks to properly synchronize access to shared resources is sufficient to ensure that a multithreaded program works properly with a preemptive virtual machine.
For cooperative threads, it is up to the programmer to ensure that threads give up the processor routinely so that they do not deprive other threads of execution time. One way to do this is to call
yield(), which moves the current thread off the processor and onto the ready queue. A second approach is to call
sleep(), which makes the thread give up the processor and does not allow it to run until the amount of time specified in the argument to sleep has passed.
As you might expect, simply placing these calls at arbitrary points in code doesn't always work. If a thread is holding a lock (because it's in a synchronized method or block of code), it does not release the lock when it calls
yield(). This means that other threads waiting for the same lock will not get to run, even though the running thread has yielded to them. To alleviate this problem, call
yield() when not in a synchronized method. Surround the code to be synchronized in a synchronized block within a non-synchronized method and call
yield() outside of that block.
Another solution is to call
wait(), which makes the processor give up the lock belonging to the object it is currently in. This approach works fine if the object is synchronized at the method level, because it is only using that one lock. If it is using a fine-grained lock,
wait() will not give up those locks. In addition, a thread that is blocked on a call to
wait() will not awaken until another thread calls
notify(), which moves the waiting thread to the ready queue. To wake up all threads that are blocking on a
wait() call, a thread calls