Introduction to Threads

Thread.setDaemon
(boolean):
The setDaemon()
function can be called on a thread to make it a daemon thread. It
must be called before the thread is started.

JVM
can exit even if some of the daemon threads have not finished
execution. However, if some non-daemon threads are remaining, then
the JVM cannot exit.

Thread
is represented by an instance of the java.lang.Thread
class. A new Thread object isn’t actually associated with an
underlying operating system thread until its start()
method is called, which allows its characteristics (name, priority,
and so forth) to be set before it starts. After start() is called,
an operating system thread is created by the Java virtual machine
and this thread begins executing the thread’s run()
method. A Thread continues to run until its run() method returns or
its interrupt()
method is called.

Threads can be implemented in 3 ways:

Extend Thread class and provide run() (optionally, start() can also be
provided.)

Implement Runnable interface and provide run().

Both methods above need to call start() method to begin execution.

Use classes Timer
and TimerTask.
Timer class has method schedule(), which takes an object of class TimerTask and a repeat time-interval.
For all TimerTask objects scheduled in a Timer, the Timer calls their run() method after the
given time-interval.
An instance of Timer runs in a single thread and sequentially executes the tasks.
It is recommend to use
ScheduledThreadPoolExecutor instead.

One
can override start() when extending thread but inside it,
super.start() must be called sometime. If not, then no new thread
will be created and code will execute sequentially. Normally, it
should not be required to override start() but one may still want to
override it if you want to say decide on whether to launch a thread
or not (can be done outside start() and run() but still) or you want
to print some debugging information just before launching new
thread.

Thread
can be stopped by calling thr.interrupt()
but if the target thread ignores InterruptedException
or does
not check interruption status by repeatedly checking
Thread.interrupted()
inside its run method, then this interruption would be of no help
and target thread will not die.
If the target thread is blocked in wait(), sleep() or join(),
then it will receive an InterruptedException.
If the thread is blocked in an I/O operation, then the channel will be closed and the thread will
receive an ClosedByInterruptedException.

Synchronized
keyword can be used to make an object/method/code-piece thread-safe
such that only one thread can access the synchronized code at one
time. Other threads wait for lock on that code and execute only when
the using thread releases the lock. Caution:
Too much synchronizing can affect performance since threads need to
do more wait/sleep and wake-up activities than required normally.
Hence it should be used minimally.

Each
thread has its own Program
Counter
(pointer to next memory instruction) and its own Stack
Space.
But all threads share the same Heap.
Heap is shared because dynamically allocated memory by a thread may
be required even after the thread has finished execution. If heap
was not shared, then memory used by one thread would become totally
unavailable to other threads or it would need explicit copying to
other threads’ memory. Stack space is not shared because if it
were shared, then pop operations from the stack would not be simple
pop but thread-respective pop. Such a pop may not just be removal of
top-most element but can be removal of other elements too since they
may belong to other threads which need to unwind their stacks.

Context
switching between threads is much simpler and faster (as compared to
processes) because of this sharing of address space. So when context
of thread is switched, OS does not need to switch whole of the
address space including heap, stack, text segment etc. It just
switches only some variables like reference to stack and program
counter etc.

Deprecated methods in Thread class: destroy(), stop() and suspend() are
deprecated methods in Thread class because they did not free the locks held by the threads
before killing/suspending them and caused other waiting threads to get blocked indefinitely.
Since resume() is dependent on suspend(), so that is also deprecated.

Thread.State getState(): This method returns one of the following values:

NEW: A thread which is yet to run.

RUNNABLE: A thread currently running.

BLOCKED: A thread that is blocked waiting to get a monitor lock (outside
the synchronized block or inside the monitor.lock() method).

WAITING: A thread that called wait() and is now waiting indefinitely for
another thread to perform a particular action (inside the synchronized block).

TIMED_WAITING: A thread that called wait(long millis) and is now waiting
for another thread to perform an action for up to a specified waiting time (inside the synchronized block).

Waiting for a thread to die

join() method can be used to wait for a thread to die.
A thread can call <running-thread>.join() to wait on the running thread.
Internally, the join method calls this.wait() in a loop conditioned on this.isAlive
When a thread terminates, it calls this.notifyAll() due to which any thread blocked on join() can proceed.
Due to this scheme, its not recommended to use a thread as a monitor by calling <some-thread>.wait() directly.
The join method has 2 other flavors: