Each thread has a life cycle that consists of several different states, which are summarized in Figure 14.4.1 and Table 14.4.2. Thread states are represented by labeled ovals, and the transitions between states are represented by labeled arrows.
Much of a thread’s life cycle is under the control of the operating system and the Java Virtual Machine. Those transitions represented by method names—such as start(), stop(), wait(), sleep(), notify()—can be controlled by the program. Of these methods, the stop() method has been deprecated because it is inherently unsafe to stop a thread in the middle of its execution. Other transitions—such as dispatch, I/O request, I/O done, time expired, done sleeping—are under the control of the CPU scheduler.
When first created a thread is in the ready state, which means that it is ready to run. In the ready state, a thread is waiting, perhaps with other threads, in the ready queue, for its turn on the CPU. A queue is like a waiting line. When the CPU becomes available, the first thread in the ready queue will be dispatched —that is, it will be given the CPU. It will then be in the running state.
Table14.4.2.A summary of the different thread states.
The thread is ready to run and waiting for the CPU.
The thread is executing on the CPU.
The thread is waiting for some event to happen.
The thread has been told to sleep for a time.
The thread is waiting for I/O to finish.
The thread is terminated.
Transitions between the ready and running states happen under the control of the CPU scheduler, a fundamental part of the Java runtime system. The job of scheduling many threads in a fair and efficient manner is a little like sharing a single bicycle among several children. Children who are ready to ride the bike wait in line for their turn. The grown up (scheduler) lets the first child (thread) ride for a period of time before the bike is taken away and given to the next child in line. In round-robin scheduling, each child (thread) gets an equal amount of time on the bike (CPU).
When a thread calls the sleep() method, it voluntarily gives up the CPU, and when the sleep period is over, it goes back into the ready queue. This would be like one of the children deciding to rest for a moment during his or her turn. When the rest was over, the child would get back in line.
When a thread calls the wait() method, it voluntarily gives up the CPU, but this time it won’t be ready to run again until it is notified by some other thread.
This would be like one child giving his or her turn to another child. When the second child’s turn is up, it would notify the first child, who would then get back in line.
The system also manages transitions between the blocked and ready states. A thread is put into a blocked state when it does some kind of I/O operation. I/O devices, such as disk drives, modems, and keyboards, are very slow compared to the CPU. Therefore, I/O operations are handled by separate processors known as controllers.
For example, when a thread wants to read data from a disk drive, the system will give this task to the disk controller, telling it where to place the data. Because the thread can’t do anything until the data are read, it is blocked, and another thread is allowed to run. When the disk controller completes the I/O operation, the blocked thread is unblocked and placed back in the ready queue.
In terms of the bicycle analogy, blocking a thread would be like giving the bicycle to another child when the rider has to stop to tie his or her shoe. Instead of letting the bicycle just sit there, we let another child ride it. When the shoe is tied, the child is ready to ride again and goes back into the ready line. Letting other threads run while one thread is waiting for an I/O operation to complete improves the overall utilization of the CPU.
True or false, round-robin scheduling is always the best way to allocate access to the CPU.