2. Implement Runnable interface
- Create a new class implementing the Runnable interface.
- Provide a public void run method.
- Create an instance of this class.
- Create a Thread, passing the instance as a target -
new Thread(object)
- Target should implement Runnable, Thread class implements
it, so it can be a target itself.
- Call the start method on the Thread.
JVM creates one user thread for running a program. This thread
is called main thread. The main method of the class is called
from the main thread. It dies when the main method ends. If
other user threads have been spawned from the main thread, program
keeps running even if main thread dies. Basically a program
runs until all the user threads (non-daemon threads) are dead.
A thread can be designated as a daemon thread by calling
setDaemon(boolean) method. This method should be called before
the thread is started, otherwise IllegalThreadStateException
will be thrown.
A thread spawned by a daemon thread is a daemon thread.
Threads have priorities. Thread class have constants MAX_PRIORITY
(10), MIN_PRIORITY (1), NORM_PRIORITY (5)
A newly created thread gets its priority from the creating
thread. Normally it'll be NORM_PRIORITY.
getPriority and setPriority are the methods to deal with
priority of threads.
Java leaves the implementation of thread scheduling to JVM
developers. Two types of scheduling can be done.
1. Pre-emptive Scheduling.
Ways for a thread to leave running state
- It can cease to be ready to execute ( by calling a blocking
i/o method)
- It can get pre-empted by a high-priority thread, which
becomes ready to execute.
- It can explicitly call a thread-scheduling method such
as wait or suspend.
- Solaris JVM's are pre-emptive.
- Windows JVM's were pre-emptive until Java 1.0.2
2. Time-sliced or Round Robin Scheduling
- A thread is only allowed to execute for a certain amount
of time. After that, it has to contend for the CPU (virtual
CPU, JVM) time with other threads.
- This prevents a high-priority thread mono-policing the
CPU.
- The drawback with this scheduling is - it creates a
non-deterministic system - at any point in time, you cannot
tell which thread is running and how long it may continue
to run.
- Mactinosh JVM's
- Windows JVM's after Java 1.0.2
Different states of a thread:
1. Yielding
- Yield is a static method. Operates on current thread.
- Moves the thread from running to ready state.
- If there are no threads in ready state, the yielded
thread may continue execution, otherwise it may have to
compete with the other threads to run.
- Run the threads that are doing time-consuming operations
with a low priority and call yield periodically from those
threads to avoid those threads locking up the CPU.
2. Sleeping
- Sleep is also a static method.
- Sleeps for a certain amount of time. (passing time without
doing anything and w/o using CPU)
- Two overloaded versions - one with milliseconds, one
with milliseconds and nanoseconds.
- Throws an InterruptedException.(must be caught)
- After the time expires, the sleeping thread goes to
ready state. It may not execute immediately after the time
expires. If there are other threads in ready state, it may
have to compete with those threads to run. The correct statement
is the sleeping thread would execute some time after the
specified time period has elapsed.
- If interrupt method is invoked on a sleeping thread,
the thread moves to ready state. The next time it begins
running, it executes the InterruptedException handler.
3. Suspending
- Suspend and resume are instance methods and are deprecated
in 1.2
- A thread that receives a suspend call, goes to suspended
state and stays there until it receives a resume call on
it.
- A thread can suspend it itself, or another thread can
suspend it.
- But, a thread can be resumed only by another thread.
- Calling resume on a thread that is not suspended has
no effect.
- Compiler won't warn you if suspend and resume are successive
statements, although the thread may not be able to be restarted.
4. Blocking
- Methods that are performing I/O have to wait for some
occurrence in the outside world to happen before they can
proceed. This behavior is blocking.
- If a method needs to wait an indeterminable amount of
time until some I/O takes place, then the thread should
graciously step out of the CPU. All Java I/O methods behave
this way.
- A thread can also become blocked, if it failed to acquire
the lock of a monitor.
5. Waiting
- wait, notify and notifyAll methods are not called on
Thread, they're called on Object. Because the object is
the one which controls the threads in this case. It asks
the threads to wait and then notifies when its state changes.
It's called a monitor.
- Wait puts an executing thread into waiting state.(to
the monitor's waiting pool)
- Notify moves one thread in the monitor's waiting pool
to ready state. We cannot control which thread is being
notified. notifyAll is recommended.
- NotifyAll moves all threads in the monitor's waiting
pool to ready.
- These methods can only be called from synchronized code,
or an IllegalMonitorStateException will be thrown. In other
words, only the threads that obtained the object's lock
can call these methods.
Locks, Monitors and Synchronization
Every object has a lock (for every synchronized code block).
At any moment, this lock is controlled by at most one thread.
A thread that wants to execute an object's synchronized code
must acquire the lock of the object. If it cannot acquire the
lock, the thread goes into blocked state and comes to ready
only when the object's lock is available.
When a thread, which owns a lock, finishes executing the
synchronized code, it gives up the lock.
Monitor (a.k.a Semaphore) is an object that can block and
revive threads, an object that controls client threads. Asks
the client threads to wait and notifies them when the time is
right to continue, based on its state. In strict Java terminology,
any object that has some synchronized code is a monitor.
2 ways to synchronize:
1. Synchronize the entire method
- Declare the method to be synchronized - very common
practice.
- Thread should obtain the object's lock.
2. Synchronize part of the method
- Have to pass an arbitrary object which lock is to be
obtained to execute the synchronized code block (part of
a method).
- We can specify "this" in place object, to obtain very
brief locking - not very common.
wait - points to remember
- calling thread gives up CPU
- calling thread gives up the lock
- calling thread goes to monitor's waiting pool
- wait also has a version with timeout in milliseconds.
Use this if you're not sure when the current thread will
get notified, this avoids the thread being stuck in wait
state forever.
notify - points to remember
- one thread gets moved out of monitor's waiting pool
to ready state
- notifyAll moves all the threads to ready state
- Thread gets to execute must re-acquire the lock of the
monitor before it can proceed.
Note the differences between blocked and waiting.
Blocked |
Waiting |
Thread is waiting to get a lock on the monitor.
(or waiting for a blocking i/o method) |
Thread has been asked to wait. (by means of
wait method) |
Caused by the thread tried to execute some synchronized
code. (or a blocking i/o method) |
The thread already acquired the lock and executed
some synchronized code before coming across a wait
call. |
Can move to ready only when the lock is available.
( or the i/o operation is complete) |
Can move to ready only when it gets notified
(by means of notify or notifyAll) |
Points for complex models:
- Always check monitor's state in a while loop, rather
than in an if statement.
- Always call notifyAll, instead of notify.
Class locks control the static methods.
wait and sleep must be enclosed in a try/catch for InterruptedException.
A single thread can obtain multiple locks on multiple objects
(or on the same object)
A thread owning the lock of an object can call other synchronous
methods on the same object. (this is another lock) Other threads
can't do that. They should wait to get the lock.
Non-synchronous methods can be called at any time by any
thread.
Synchronous methods are re-entrant. So they can be called
recursively.
Synchronized methods can be overrided to be non-synchronous.
synchronized behavior affects only the original class.
Locks on inner/outer objects are independent. Getting a lock
on outer object doesn't mean getting the lock on an inner object
as well, that lock should be obtained separately.
wait and notify should be called from synchronized code.
This ensures that while calling these methods the thread always
has the lock on the object. If you have wait/notify in non-synchronized
code compiler won't catch this. At runtime, if the thread doesn't
have the lock while calling these methods, an IllegalMonitorStateException
is thrown.
Deadlocks can occur easily. e.g, Thread A locked Object A
and waiting to get a lock on Object B, but Thread B locked Object
B and waiting to get a lock on Object A. They'll be in this
state forever.
It's the programmer's responsibility to avoid the deadlock.
Always get the locks in the same order.
While 'suspended', the thread keeps the locks it obtained
- so suspend is deprecated in 1.2
Use of stop is also deprecated, instead use a flag in run
method. Compiler won't warn you, if you have statements after
a call to stop, even though they are not reachable.
Previous
Contents
Next