C SC 340 Lecture 4: Threads
[ previous | schedule | next ]
A thread is a single sequence of instruction executions within a process
Anything that can be done with multiple threads can also be done with multiple single-threaded processes. So why use threads?
If the OS itself supports thread creation, scheduling, and all-around management, the technique is called kernel threads.
If thread support and management is available to the programmer through library of functions, the technique is called user threads.
User threads can be used regardless of whether or not the OS supports kernel threads.
Only through kernel threads can the advantages listed above be fully realized. For example, if kernel threads are not supported and a user thread requests I/O then the whole process blocks because the OS only knows about processes. With kernel threads, the OS can block only the thread that requested I/O and allow other threads in the same process to run.
Nearly every modern OS supports kernel threads.
An OS that supports both kernel threads and multiple processors may schedule different threads from the same process to run in parallel on different processors.
If the OS does not support kernel threads but a library for user threads is available, we say the user-to-kernel relationship is many-to-one because many user threads are mapped to one "kernel thread" - the single-threaded process. Thread management is handled by the library, not the OS. If the process blocks, so do all threads.
The many-to-one model may be used even if the host OS does support kernel threads, if the library does not attempt to use them.
If both user and kernel threads are available, the OS may implement kernel threads in either of two methods:
A thread pool is a technique a process can employ to manage its threads. It addresses two issues: 1. thread creation overhead and delay are incurred even if the thread is used just once then discarded, and 2. the lack of upper bound on number of concurrent threads unless managed.
The process initially creates a fixed number of threads, called the pool, then when a request needs to be serviced the process will assign it to an available thread from the pool. If all threads are busy, the request must wait. When a thread is finished servicing its assigned request, it becomes available again.
More sophisticated thread pool management is possible, such as dynamically adjusting the size of the pool according to real and anticipated demand.
See http://www.ibm.com/developerworks/library/j-jtp0730.html for a very informative 2002 article on thread pools in general and in Java.
Java provides user threads through its Thread class and Runnable interface. Java programs are generally not compiled into machine code but instead run on the Java Virtual Machine (JVM). The JVM specification does not address whether or how user threads are to be mapped to kernel threads. This is because the developers of the JVM want it to be implemented on as many OSs as possible. Typical Windows implementation of JVM maps Java threads to Win32 kernel threads; typical Linux implementation maps then to POSIX Pthreads library threads - kernel thread support depends on how Pthreads is implemented (Pthreads itself is an API, e.g. a specification).
The possible states of a Java thread are: new, runnable, blocked, or dead. This table summarizes the analogy of these thread states to the process states we already covered.
state equivalencies | |
---|---|
process state | thread state |
new | new |
ready | runnable |
running | runnable |
waiting | blocked |
terminated | dead |
There are two "equivalent" techniques for defining a class that uses Java threads. For illustration, let's call the class MyThread. The two techniques are:
Note: The client does not call the run() method directly! The client calls the start() method which does some housekeeping then calls run()
Note: Normally the client will not need to re-use the threads so will skip the use of variables. For the first technique, the client code would be new MyThread().start(); and for the second technique the client code would be new Thread(new MyThread()).start();
The advantage of the second technique is it allows myThread to be a subclass of some other class. The first technique prohibits this, since a Java class can only extend one class and in this case it extends Thread.
There are two flavors of Java threads, user threads and daemon threads. The only difference between them is this: when JVM detects that an application's only running threads are daemon threads, it will immediately shut them and the application down. If there is at least one user thread, all threads and the application are allowed to continue.
When your Java program begins to run, a user thread is created to run main(). Any threads it creates inherit its status as a user thread. A thread can become a daemon thread only by calling setDaemon(true) before starting it, or if it is created by a daemon thread.
Interesting fact: When your GUI application first creates a JFrame (or any other descendant of java.awt.Window), the JVM creates several housekeeping threads in the background. Since this usually occurs in the main thread, these housekeeping threads are user threads. Thus when main() terminates, they continue to live. This is why your GUI application does not halt when main() terminates. The GUI application will halt only when System.exit() is invoked.