THREADS in Java
All you need to know about Threads in Java
PREREQUISITES
The reader should have minimum knowledge of
·
Basic
concepts of Object Oriented programming
·
What
is exception handling and its importance
Points to remember
Creating,
Instantiating, and Starting New Threads
·
Java
is fundamentally multi-threaded.
·
Threads
can be created by extending Thread and overriding the public void run() method.
·
Thread
objects can also be created by calling the Thread constructor that takes a
Runnable argument. The Runnable object is said to be the target of the
thread.
·
You
can call start() on a Thread object only once. If start() is called more than
once on a Thread object, it will throw a RuntimeException.
·
It
is legal to create many Thread objects using the same Runnable object as the
target.
·
When
a Thread object is created, it does not become a thread of execution until
its start() method is invoked. When a Thread object exists but hasn’t been
started, it is in the new state and is not considered alive.
·
When
a thread begins execution, the scheduler calls its run method.
Signature of run method – public void run
()
·
When
a thread returns from its run method its
dead. It cannot be restarted, but its methods can be called. (it’s just an
object no more in a running state)
·
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.
Transitioning
Between Thread States – Thread Life Cycle
·
Once
a new thread is started, it will always enter the runnable state.
·
The
thread scheduler can move a thread back and forth between the runnable state
and the running state.
·
Only
one thread can be running at a time, although many threads may be in
the runnable state.
·
There
is no guarantee that the order in which threads were started determines the
order in which they’ll run.
·
There’s
no guarantee that threads will take turns in any fair way. It’s up to
the
thread scheduler, as determined by the particular virtual machine implementation.
If you want a guarantee that your threads will take turns regardless of the
underlying JVM, you should can use the sleep() method. This prevents one thread
from hogging the running process while another
thread starves.
·
A
running thread enters a blocked/waiting state by a wait(), sleep(), or join()
call.
·
A
running thread may enter a blocked/waiting state because it can’t acquire the
lock for a synchronized block of code.
·
When
the sleep or wait is over, or an object’s lock becomes available, the thread
can only reenter the runnable state. It will go directly from waiting
to running (well, for all practical
purposes anyway).
·
A
dead thread cannot be started again.
·
If
start is called again on a dead thread, IllegalThreadStateException is
thrown.
Thread
Priority, Sleep, Yield, Join, wait, notify and notifyAll
·
Thread Priority
·
getPriority and setPriority are the methods to deal with
priority of threads.
·
The
sleep() method is a static method that sleeps the currently executing
thread. One thread cannot tell another
thread to sleep.The setPriority()
method is used on Thread objects to give
threads apriority of between 1(low) and
10 (high), although priorities are not guaranteed,and not all JVMs use a
priority range of 1-10.
·
If
not explicitly set, a thread’s priority will be the same priority as the thread
that created this thread (in other words, the thread executing the code that
creates the new thread). Normally it’ll be NORM_PRIORITY.
·
At
any given time, when a thread is running it will usually not have a lower
priority than any thread in the runnable state. If a low-priority thread is
running when a high-priority thread enters runnable, the JVM will preempt the
running low-priority thread and put the high-priority thread in.
·
Different states
of a thread:
1. Yield
·
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. Sleep
·
Sleep is also a static method.
·
Sleep()
is used to delay execution for a period of time, and no locks are
released
when a thread goes to sleep. (passing time without doing anything and w/o using CPU)
·
Two overloaded versions – one with milliseconds, one
with milliseconds and nanoseconds.
·
Sleep 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. 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.
4. Communicating with objects by using Wait notify and
notifyAll
·
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) The
wait() method lets a thread say, “there’s nothing for me to do here,so put me
in your waiting pool and notify me when something happens thatI care about.”
Basically, a wait() call means “wait me in your pool,” or“add me to your
waiting list.”
·
Notify moves one thread in the monitor’s waiting pool to
ready state. We cannot control which thread is being notified. notifyAll is
recommended.
·
The
method notifyAll() works in the same way as notify(), only it sends the signal
to all of the threads waiting on the object. 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.
·
All
three methods—wait()/notify()/notifyAll()—must be called
from
within a synchronized context! A thread invokes wait()/notify()
on
a particular object, and the thread must currently hold the lock on that
object.
5. Join
·
When
one thread calls the join() method of another thread, the currently running
thread will wait until the thread it joins with has completed. Think of the
join() method as saying, “Hey thread, I want to join on to the end of you. Let
me know when you’re done, so I can enter the runnable state.”
Concurrent
Access Problems and Synchronized Threads
·
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 (or 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.
·
Synchronized
methods prevent more than one thread from accessing an object’s critical method
code.
·
You
can use the synchronized keyword as a method modifier, or to start a
synchronized block of code.
·
To
synchronize a block of code (in other words, a scope smaller than the
whole
method), you must specify an argument that is the object whose lock you want to
synchronize on.
·
While
only one thread can be accessing synchronized code of a particular
instance,
multiple threads can still access the same object’s unsynchronized code.
·
When
an object goes to sleep, it takes its locks with it.
·
Static
methods can be synchronized, using the lock from the java.lang.Class instance
representing that class.
Deadlocked
Threads
·
Deadlocking
can occur easily ie when a locked object attempts to access another locked object
that is trying to access the first locked object. In other words, both threads
are waiting for each other’s locks to be released; therefore, the locks will never
be released!
·
Example: 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.
Tips
·
There
are two ways to implement threads.
1.
Extend
Thread class
·
Create
a new class, extending the Thread class.
·
Provide
a public void run method, otherwise empty run in Thread class will be executed.
·
Create
an instance of the new class.
·
Call
start method on the instance (don’t call run – it will be executed on the same
thread)
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.
·
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
·
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.
·
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.
·
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.
·
It’s the programmer’s responsibility to avoid the
deadlock. Always get the locks in the same order.Deadlocking is bad. Don’t do it.
Best Practices
- Avoid Synchronization where possible
- Avoid
synchronization in read-only or single-threaded query-call
- Synchronized
method slightly faster than synchronized block
- Consider
using non-synchronized classes and synchronized-wrapper (performance vs.
maintenance)
- Poll
only for outside events, perform only in "side" thread; use
wait()/notify() instead
- Prioritize
threads; use notify() instead of notifyAll()
- Keep
synchronization out of loop
- Don't
turn off native threads
- Consider
using ThreadLocal to provide threaded access to singletons with state (?)
- Maximize
thread lifetime, minimize thread creation/destruction cycles; use thread
pool
- Minimize
contention for shared resources
- Create/use
read/write locks (monitor) instead of simple synchronization
- Avoid
monopolizing shared resources
Comments
Post a Comment