14.2. What Is a Thread?A thread (or thread of execution or thread of control) is a single sequence of executable statements within a program. For Java applications, the flow of control begins at the first statement in main() and continues sequentially through the program statements. For Java applets, the flow of control begins with the first statement in init(). Loops within a program cause a certain block of statements to be repeated. If-else structures cause certain statements to be selected and others to be skipped. Method calls cause the flow of execution to jump to another part of the program, from which it returns after the method's statements are executed. Thus, within a single thread, you can trace the sequential flow of execution from one statement to the next. One way to visualize a thread is to imagine that you could make a list of the program's statements as they are executed by the computer's central processing unit (CPU). Thus, for a particular execution of a program with loops, method calls, and selection statements, you could list each instruction that was executed, beginning at the first, and continuing until the program stopped, as a single sequence of executed statements. That is a thread!
Visualizing a thread Now imagine that we break a program up into two or more independent threads. Each thread will have its own sequence of instructions. Within a single thread, the statements are executed one after the other, as usual. However, by alternately executing the statements from one thread and another, the computer can run several threads concurrently. Even though the CPU executes one instruction at time, it can run multiple threads concurrently by rapidly alternating among them. The main advantage of concurrency is that it allows the computer to do more than one task at a time. For example, the CPU could alternate between downloading an image from the Internet and running a spreadsheet calculation. This is the same way you ate toast and cereal and drank coffee in our earlier breakfast example. From our perspective, it might look as if the computer had several CPUs working in parallel, but that's just the illusion created by effectively scheduling threads. Java Language Rule: JVM Threads
Java Language Rule: Garbage Collector Thread
14.2.1. Concurrent Execution of ThreadsThe technique of concurrently executing several tasks within a program is known as multitasking. A task in this sense is a computer operation of some sort, such as reading or saving a file, compiling a program, or displaying an image on the screen. Multitasking requires the use of a separate thread for each of the tasks. The methods available in the Java Thread class make it possible (and quite simple) to implement multithreaded programs.
Multitasking Most computers, including personal computers, are sequential machines that consist of a single CPU capable of executing one machine instruction at a time. In contrast, parallel computers, used primarily for large-scale scientific and engineering applications, are made up of multiple CPUs working in tandem. Today's personal computers, running at clock speeds over 1 gigahertz (i.e., more than 1 billion cycles per second) are capable of executing millions of machine instructions per second. Despite its great speed, however, a single CPU can process only one instruction at a time. A CPU uses a fetch-execute cycle to retrieve the next instruction from memory and execute it. Since CPUs can execute only one instruction at a time, multithreaded programs are made possible by dividing the CPU's time and sharing it among the threads. The CPU's schedule is managed by a scheduling algorithm, which is an algorithm that schedules threads for execution on the CPU. The choice of a scheduling algorithm depends on the platform on which the program is running. Thus, thread scheduling is handled differently on Unix, Windows, and Macintosh systems. One common scheduling technique is known as time slicing, in which each thread alternately gets a slice of the CPU's time. For example, suppose we have a program that consists of two threads. Using this technique, the system would give each thread a small quantum of CPU timesay, one thousandth of a second (1 millisecond)to execute its instructions. When its quantum expires, the thread would be preempted and the other thread would be given a chance to run. The algorithm would then alternate in this round-robin fashion between one thread and the other (Fig. 14.1). During each millisecond on a 300-megahertz CPU, a thread can execute 300,000 machine instructions. One megahertz equals 1 million cycles per second. Thus, within each second of real time, each thread will receive 500 time slices and will be able to execute something like 150 million machine instructions. Figure 14.1. Each thread gets a slice of the CPU's time. |
Depending on the hardware platform, Java threads can be supported by assigning different threads to different processors, by time slicing a single processor, or by time slicing many hardware processors. |
Let's consider a simple example of a threaded program. Suppose we give every individual thread a unique ID number, and each time it runs, it prints its ID 10 times. For example, when the thread with ID 1 runs, the output produced would just be a sequence of 10 1's: 1111111111.
As shown in Figure 14.2, the NumberThread class is defined as a subclass of Thread and overrides the run() method. To set the thread's ID number, the constructor takes a single parameter that is used to set the thread's ID number. In the run() method, the thread simply executes a loop that prints its own number 10 times:
public class NumberThread extends Thread { int num; public NumberThread(int n) { num = n; } public void run() { for (int k=0; k < 10; k++) { System.out.print(num); } // for } // run() } // NumberThread class
Now let's define another class whose task will be to create many NumberThreads and get them all running at the same time (Fig. 14.3). For each NumberThread, we want to call its constructor and then start() it:
public class Numbers { public static void main(String args[]) { // 5 threads NumberThread number1, number2, number3, number4, number5; // Create and start each thread number1 = new NumberThread(1); number1.start(); number2 = new NumberThread(2); number2.start(); number3 = new NumberThread(3); number3.start(); number4 = new NumberThread(4); number4.start(); number5 = new NumberThread(5); number5.start(); } // main() } // Numbers class
Thread subclass
When a thread is started by calling its start() method, it automatically calls its run() method. The output generated by this version of the Numbers application is as follows:
11111111112222222222333333333344444444445555555555
Starting a thread
From this output, it appears that the individual threads were run in the order in which they were created. In this case, each thread was able to run to completion before the next thread started running.
What if we increase the number of iterations that each thread performs? Will each thread still run to completion? The following output was generated for 200 iterations per thread:
111111111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111111111112222222 222222222222222222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222222222223333333333333333333333333 333333333333333333333333333333333333333333333333333333333333333333333 333333333333333333333333333444444444444444444444444444444444444444444 444444444444444444444444444444444444444444444444444444444444444444444 444444444455555555555555555555555555555555555555555555555555555555555 555555555555555555555555555555555555555555555555555555555555552222222 222233333333333333333333333333333333333333333333333333333333333333333 333333333333334444444444444444444444444444445555555555555555555555555 555555555555555555555555555555555555555555555555555555444444444444444 4444444444444444444444444444444444
In this case, only thread 1 managed to run to completion. Threads 2, 3, 4, and 5 did not. As this example illustrates, the order and timing of a thread's execution are highly unpredictable. The example also serves to illustrate one way of creating a multithreaded program:
Create a subclass of the Thread class.
Within the subclass, implement a method with the signature void run() that contains the statements to be executed by the thread.
Create several instances of the subclass and start each thread by invoking the start() method on each instance.
Java Language Rule: Thread Creation
One way to create a thread in Java is to define a subclass of Thread and override the default run() method. |