|
|
|
|
|
|
|
|
|
Computers perform operations concurrently, such as compiling a program, printing a file, and receiving e-mail messages over a network. Most languages (including C++) generally provide only a simple set of control structures that enable programmers to perform one action at a time , then proceed to the next action after finishing the previous one. The congruency that computers perform today is implemented as operating system "primitives" available only to highly experienced "systems programmers." The Ada Programming language developed by the United States Department of Defense made congruency primitives available to defense contractors building command and control systems. However, Ada was not used in Academics and in Commerce. Java is the only popular language that makes congruency primitives available to the applications programmer. The programmer specifies that applications contain threads of execution, each thread designating a portion of a program that may execute concurrently with other threads. Java includes multithreading primitives as part of the language itself, in classes Thread, ThreadGroup, ThreadLocal, and ThreadDeath of the java.lang package. Java multithreading is platform dependent. So a multithreaded application behaves differently on different Java implementations. The problem with single-threaded applications is that lengthy activities must complete before other activities can begin. In a multi-threaded application, threads can share a processor (or a set of processors), so that multiple tasks are performed in parallel.
Java provides a low-priority garbage collector thread that reclaims dynamically allocated memory that is no longer needed. The garbage collector thread runs when processor time is available and there are no higher priority runable threads. The garbage collector runs immediately when the system is out of memory to try to reclaim memory. Setting an object reference to null marks that object for eventual garbage collection (if there are no other references to that object.).
Method run contains the code that controls a thread's execution. Start method launches the application and returns to the caller. Do not start an already started thread. The static method sleep is called with an argument specifying how long the currently executing thread should sleep (in milliseconds) A sleeping thread does not contend for a processor allowing lower priority threads to run. The interrupt method interrupts a thread. Method isAlive returns true if start has been called for a given thread and the thread is not dead. Method setName sets a thread's name, while getName returns its name. Method toString returns a String consisting of the name of the thread, its priority, and its group. The static method currentThread returns a reference to the currently executing Thread. Method join waits for the Thread to which the message is sent to die before the calling Thread can proceed. (No argument indicates that the current Thread will wait forever for the target Thread to die before the calling Thread proceeds).
Life Cycles of a thread.
A thread can be in several states. It is
in the born state when created, and remains in that state until
the program calls method start. Now the thread enters
the ready (also called the runable) state. Here the
highest priority thread enters the running (that is begins executing)
state, when the system assigns a processor to the thread. A thread
enters the dead state when its run method completes or terminates
for any reason. A dead thread will be disposed of by the system.
A running thread becomes blocked if it issues an input/output
request (takes time). A blocked thread cannot use a processor even
if one is available. A thread enters a sleeping state when the program
calls method sleep. A sleeping thread becomes ready after
the designated sleep time expires. A sleeping thread cannot use a
processor, unless the program calls method interrupt, when it exits
the sleeping state and is ready to execute.
For more information: Multithreading
Thread Priorities and Scheduling.
Every Java thread has a priority in the range 1 (Thread.MIN_PRIORITY) and 10 (Thread.MAX_PRIORITY). The default priority is 5 ((Thread.NORM_PRIORITY). Each new thread inherits the priority of the thread that creates it. Some Java platforms support a concept called timeslicing and some do not. Without timeslicing, each thread in a set of equal-priority threads run to completion (unless the thread leaves the running state and enters the waiting, sleeping, or blocked state, or the thread gets interrupted by a higher priority thread) before the thread's peers get a chance to execute. With timeslicing, each thread receives a brief burst of processor time called a quantum during which that thread can execute, after which, the operating system takes the processor away from that thread and gives it to the next thread of equal priority (if one is available).
The job of the Java scheduler is to keep the highest priority thread running at all times, and if timeslicing is available, to ensure that each high-priority thread execute for a quantum in round-robin fashion. Newer higher-priority threads can postpone, even indefinitely the execution of lower priority threads. Indefinite postponement is called starvation. A thread's priority is adjusted with method setPriority, which takes an int argument (1 to 10). Method getPriority returns the thread's priority. A thread can call the yield method to give other threads of equal priority a chance to execute on a platform without timeslice. When a higher priority thread is ready, the operating system preempts the current thread. A thread executes until it dies, is blocked, sleeps, waits, yields, is preempted, or its quantum expires.
| Java Uses monitors. (as discussed by C. A. R. Hoare in his 1974 paper)
to perform synchronization. The monitor lets only one thread at a
time execute a synchronized method on the object. This is
accomplished by locking the object. If there are several synchronized
methods,
only one synchronized method may be active on an object at once;
all other threads attempting to invoke synchronized methods must
wait. When a synchronized method finishes executing, the lock
on the object is released and the monitor lets the highest priority-ready
thread attempting to invoke a synchronized method proceed.
Prof. Charles Antony Richard Hoare
|
A thread executing in a synchronized may determine that it cannot proceed, so the thread voluntarily calls wait, thus removing this thread from contention for the processor , and waits. When a thread executing a synchronized method completes or satisfies the condition on which another thread may be waiting, this thread can notify it to become ready again. If a thread calls notifyall, all waiting threads are placed in a ready state, although only the highest priority one can obtain the lock, while other threads are blocked. Since methods wait, notify, and notifyall are all inherited by all classes from class object, any object may have a monitor.
Threads in a waiting state must be awakened explicitly with notify or the thread will wait forever, causing a deadlock. So make sure that every call to wait has a corresponding notify or notifyall call. Upon completion of a synchronized method, outside threads that were blocked because the monitor was busy can proceed to enter the object. However, threads that invoked wait can proceed only when notified by another thread.
For more information: Synchronization
Producer/Consumer Relationship without Thread Synchronization.
In a producer/consumer relationship, a producer thread calling a produce method may see that the consumer thread has not read the last message from a shared region of memory called a buffer, so the producer thread will call wait. When a consumer thread reads the message, it will call notify to allow a waiting producer to proceed.. When a consumer thread enters the monitor and finds the buffer empty, it calls wait. A producer finding the buffer empty writes the buffer, then calls notify so a waiting consumer can proceed.
Shared data can get corrupted if we do not synchronize access among multiple threads. Data can be lost if the producer places new data into the slot before the consumer consumes the previous data, and doubled if the consumer consumes data again before the producer produces the next item.
Producer/Consumer Relationship with Thread Synchronization.
With synchronization, the consumer only consumes after the producer produces a value. If writeable is true, setSharedInt can place a value into variable sharedInt, as that variable is empty. But getSharedInt cannot read the value in sharedInt. If writeable is false, getSharedInt can read a value from variable sharedInt, as that variable is no longer empty. But setSharedInt cannot place a value in sharedInt.
The while structure tests the writeable variable with the condition !writeable. If this condition is true, the thread invokes method wait. This places the ProduceInteger thread object into a waiting state allowing another thread to invoke as Synchronized method. The ProduceInteger object remains in the waiting state until it is notified that it may proceed, when it enters the ready state and waits for the system to assign it a processor. It continues executing in the while structure. When there are no more statements, it reevaluates the while condition. If the condition is false, the producer sets sharedInt to a new value, assigns value to sharedInt, sets writeable to false to indicate that the shared memory is full (that is a consumer can read the value and a producer cannot put another value there) and invokes method notify. If there are any waiting threads, it enters the ready state. The notify method returns and method setSharedInt returns to its caller.
If writeable is true there is nothing to consume, the thread invokes method wait, placing ConsumeInteger object into the waiting state until it is notified that it may proceed, when it enters the ready state and waits for a processor. It continues executing in the while structure. When there are no more statements, it reevaluates the while condition. If the condition is false, the program sets writeable to true to indicate that the shared memory is empty (that is a producer can place a value and a consumer cannot read a value) and invokes method notify. If there are any waiting threads, it enters the ready state. The notify method returns and method setSharedInt returns to its caller.
Producer/Consumer Relationship: The Circular Buffer
As threads run asynchronously, their relative speeds cannot be predicted. The producer cannot produce faster than the consumer can consume, unless we use a circular buffer that has enough cells to handle the "extra" production. The consumer only consumes a value when there are one or more values in the buffer, and the producer only produces a value when there are one or more available cells in the buffer.
The nature of multithreaded programming prevents the programmer from knowing exactly when a thread will execute. writeable indicates whether a producer can write into the circular buffer, readable indicates whether a consumer can read from the circular buffer, readLocation indicates the current position from which the consumer can read the next value, writeLocation indicates the next location in which the producer can place a value, readable is true indicates at least one value that can be read, writeable is true indicates at least one pen position in which a value can be placed..
If writeLocation is equal to readLocation, the circular buffer is full, so writeable is set to false and the program displays the string BUFFER FULL, and method notify is invoked to move a waiting thread into the ready state. If readLocation is equal to writeLocation, the circular buffer is empty, so readable is set to false and the program displays the string BUFFER EMPTY, and method notify is invoked to move a waiting thread into the ready state, and return the retrieved value to the calling method.
A daemon thread is a thread that runs for the benefit of other threads. They do not prevent a program from terminating. The garbage collector is a daemon thread. We designate a thread as a daemon with the method call setDaemon( true); A false argument means that the thread is not a daemon thread. A program can include a mixture of daemon and nondaemon threads. When only daemon threads remain in a program, it exits. Do not start a thread and then make it daemon.
For more information: Daemon
Threads Daemon
Threads
For an experiment daemon threads: Daemon
Thread Experiment
Class ThreadGroup contains many methods for processing groups
of threads.
1. Method activeCount reports the number of
active threads in a thread group plus the number of active threads in all
its child thread groups.
2. Method enumerate has four versions.
Two versions copy into an array of Thread references the active
threads in the ThreadGroup. Two versions copy into an array
of ThreadGroup references the active child thread groups.
3. Method getMaxPriority returns the maximum
priority of a ThreadGroup. Method setMaxPriority sets
a new priority for a ThreadGroup.
4. Method getName returns as a String the
ThreadGroup's
name.
5. Method getParent determines the parent
of a thread group.
6. Method parentOf returns true if
the ThreadGroup to which the message is sent is the parent of, or
the same as, the ThreadGroup supplied as an argument and returns
false
otherwise.