Threads
Info from Deital Java How to Program
It is common for desktop computers to do multiple tasks at once,
such
as receive email, send a document to a printer and edit a file.
Historically, concurrency (performing
multiple tasks simultaneously) has required use of operating system
primitives.
Java makes concurrency primitives available to application
programmers.
- Specify threads of execution
which can execute concurrently
- Capability is called multithreading.
- Only computers that have multiple processes can truly execute
operations concurrently.
- Operating systems on single-processor computers use various
techniques to simulate concurrency.
- Because Java includes multithreading primitives as part of the
language, manipulating threads is done in a portable manner across
operating systems.
- However, since operating systems handle threads differently, the
behavior of multithreaded programs may vary somewhat between platforms.
- One goal is to avoid requiring all other tasks to wait while one
lengthy process completes. Common uses are when downloading large
files, automatic garbage collection,
- Thread scheduling is the
process by which each thread is allowed use of the processor.
- Each thread has a priority
that helps determine the order in which threads are scheduled.
- Some systems use timeslicing
whereby each thread is given a limited amount of time (a quantum) to execute. When
that time expires, the thread waits while other threads (of equal
priority) execute. This is performed in a round-robin fashion.
In Java, threads move through a variety of thread states:
- Born - start of life
cycle for new thread. Will remain in this state until the program
calls the Thread method start.
- Ready/runnable - after start is invoked, thread can be
run. Thread will execute concurrently with thread that invoked
start.
- Running - state when
operating system assigns a processor to the thread, known as dispatching the thread.
- Dead - state when run method completes or is
terminated by uncaught exception. Garbage collector can remove
from memory (if no current links to that thread).
- Blocked - state a
thread enters when it attempts to perform a task that cannot be
completed immediately. For example, OS can block a thread while waiting
for an I/O request. When task completes, thread becomes Ready again and waits to be
dispatched.
- Waiting - If a
thread encounters code that it cannot execute yet (e.g., when some
condition needs to be satisfied), thread can call Object method wait to transition to Waiting state. Will remain
in that state until another thread invokes Object method notify or notifyAll on object for which thread
is waiting. Notify
transitions one thread, NotifyAll
transitions all waiting threads back to Ready. Thread
notification is platform dependent.
- The Thread method interrupt can be use to set a
thread's interrupted flag,
- Sleeping - a Running thread can call Thread method sleep to transition to Sleeping. It will transition
back to Ready when the sleep
time expires.
- If a thread cannot execute until another thread terminates, the
dependent thread can call the other thread's join method. For two joined
threads, the dependent thread leaves the Waiting state when the other thread
becomes Dead.
Thread Priorities and Thread Scheduling
- Threads have a priority in the range of Thread.MIN_PRIORITY (1)
to Thread.MAX_PRIORITY (10)
- Default is Thread.NORM_PRIORITY (5)
- Under timeslicing, a low priority task can experience starvation, if higher-priority
tasks continue to arrive and use all the processor time.
- A thread's priority can be adjusted with the method setPriority.
- A thread can call method yield
to give other threads a chance to execute. A thread cannot yield
to a higher priority thread, because higher priority tasks typically preempt lower threads. On a
timesliced machine, yielding is typically unnecessary.
- A thread executes until it dies; becomes blocked for I/O etc.;
calls sleep, wait or yield; is preempted by a higher
priority thread or has its quantum expire.
- A thread becomes Ready
when it is started, when it wakes up from Sleeping state, when notify or notifyAll is called on object on
which wait was previously
called.
Thread Synchronization
- Thread synchronization is needed when multiple threads manipulate
a shared object.
- For read-only access, synchronization is not needed.
- If one or more threads modify the object, results can be
unpredictable if not synchronized.
- Solution: give one thread at a time access to code that modifies
the object. Called thread
synchronization.
- Java uses monitors to
perform synchronization.
- A program obtains a lock
when it enters the synchronized state.
- If other threads attempt to enter a synchronized statement, they
are Blocked until the first
item is finished.
- When lock is released, the highest priority blocked thread is
allowed to proceed.
- Every Object has a monitor. Methods wait, notify and notifyAll are inherited from Object.
- Deadlock occurs if two
threads are waiting for each other.
Producer/Consumer with and without synchronization
Producer/Consumer with synchronization
- No change to Producer and Consumer
- Use synchronized methods set and get, so only one thread can access
at a time.
- A condition variable can be used to determine
whose turn it is.
- When a thread is Waiting,
it releases its lock on the Object (otherwise it could never complete,
because other threads couldn't update the Object).
- When a thread returns to Running
state, it attempts to reacquire the lock. Execution then begins
with the line after the wait.
If the resource can now be used, the process continues.
Otherwise, it may have to wait
again. This code should be in a loop, so that condition variable
is retested.
- After Object is updated, notify
(or notifyAll) should be
called, so any waiting threads will be able to execute..
Runnable
- Should be should be implemented by any class whose instances are
intended to be executed by a thread.
- The class must define a method of no arguments called
run.
- A class that implements
Runnable can run without
subclassing Thread by instantiating a Thread
instance and passing itself in as the target. In most cases, the Runnable
interface should be used if you are only planning to override the run()
method and no other Thread methods. This is important
because classes should not be subclassed unless the programmer intends
on modifying or enhancing the fundamental behavior of the class.