Threads

Multithreaded applications extend the idea of multitasked processes by taking them a level lower: individual programs now appear to do multiple tasks at the same time. The essential difference is that while each process has a complete set of its own variables, threads share the same data segment. It takes much less overhead to create and destroy threads than it does to launch new processes; and cross-process communication (even when permitted) is much slower than cross-thread communication.

A thread consists of three main parts: the virtual CPU, the code this CPU is executing, and the data that the code is working on. In Java, the virtual CPU functionality (and complexity) is encapsulated in the Thread class. The code and the data are architected in the application. It is important to note that these three dimensions are effectively independent. A thread can execute code that is the same as another thread's code, or it can execute different code. A thread can have access to the same data or different data.


Thread states

Threads are controlled by triggering state transitions. Thread states are: A thread is "put into play" by calling its start() method. This triggers a transition to the ready state. Every thread has a priority. The priority is an integer from 1 to 10. The default priority is 5, but all newly created threads are assigned the priority of their creating thread. getPriority() and setPriority() can be used to query and manipulate the priority.
The specifics of how thread priorities affect scheduling are platform dependent. The Java specification states that threads must have priorities, but it does not dictate precisely what the scheduler should do about priorities. This vagueness is a problem - algorithms that rely on manipulating thread priorities are unpredictable. [Roberts, p198]   Generally however, all threads in the ready state are placed in the FIFO queue corresponding to their priority, and the scheduler moves the first thread on the highest priority queue to the running state. [Sun275, p13-7]
On Solaris, the thread scheduler is "preemptive". A thread runs until it either ceases to be runnable, or another thread of higher priority becomes runnable. In the latter case, the current thread is "preempted" by the thread of higher priority. On Windows, the thread scheduler is "time-sliced". A thread is only allowed to execute for a limited amount of time. It is then moved to the ready state, where it must contend with all other runnable threads. [Roberts, p205]

Given that Java code needs to "run everywhere", applications should not assume "time-sliced" scheduling, and must ensure that other threads are given a chance to execute. The next several sections discuss "the art of juggling many simultaneous threads".

Yielding

yield() will allow waiting threads of equal or higher priority to execute. If there are no such threads, the current thread does not stop executing. yield() is a static method of class Thread. It always acts upon the current thread (you don't have to have an object reference in order to call it).

Suspending

A thread that receives a suspend() message enters the suspended state until it receives a resume() message or a stop() message. A thread can be suspended by itself or another thread, but it can only be resumed by another thread. Unlike yield(), both suspend() and resume() are non-static methods.

Sleeping

sleep() is a static method that causes the current thread to become dormant for the specified period of milliseconds (at a minimum). After the time-out value expires (or the thread is explicitly sent an interrupt() message), the thread is moved back to the ready state, and begins running again whenever the scheduler decides.
Note that sleep() and yield() are both static. They operate on the currently running thread. Also note that a call to sleep() allows threads of lower priority a chance to execute, and the yield() method only gives threads of the same or higher priority a chance.

Blocking

All Java I/O methods cause the invoking thread to surrender the CPU if the request cannot be immediately fulfilled. When the interaction with the outside world completes, the thread transitions back to the ready state.

The join() method causes the current thread to wait until the thread on which join() is called terminates. This feature can be used to delay one thread until a "timer" thread completes.

Waiting

In the diagram, the waiting state is separated from the other "dormant" states to emphasize that it is very different. The wait() method causes a running thread to surrender the CPU. notify() and notifyAll() cause waiting threads to return to the ready state. These methods are not implemented in the class Thread but in the class Object. The use of these methods is very subtle, and is discussed in a later section.