Processors these days have multiple cores, which are multiple execution units in the same processor. To make the best use of these multi-cores, we need to run tasks or threads in parallel. In other words, we need to make our programs multi-threaded (or concurrent).
Multiple threads can run in the context of the same process and thus share the same resources. How multiple thread share same resource you check this post Heap and Memory.
Performing activities in parallel enhances the responsiveness of the application, and thus the user experience.Such parallel activities can be implemented as threads: running multiple threads in parallel at the same time is called multi-threading or concurrency.
For instance, MS word office executed many task at the same time - responding to the user, checking spellings, carrying out formatting and certain associated background tasks, etc. That only possible with multiple threading.
|
thread |
Classes that provide necessary support for concurrency in Java
- Object : has methdods wait(), notify(), notifyAll() etc which are useful for multi-threading.
- Thread class
- Runnable interface
As you every class extends Object class, so all object have some basic multi-threading capability.
Methods detail that are helpful in Multi-threading
Method |
Method Type |
Short Description |
Thread currentThread() |
Static method |
Returns reference to the current
thread. |
String getName() |
Instance method |
Returns the name of the current
thread. |
int getPriority() |
Instance method |
Returns the priority value of the
current thread. |
void
join(), void
join(long), void join(long,
int) |
Overloaded
instance methods |
The current thread invoking join on
another thread waits
until the other thread dies. You can optionally give the
timeout in milliseconds (given in long) or timeout in
milliseconds as well as nanoseconds (given in long and int). |
void run() |
Instance method |
Once you start a thread (using the
start() method), the
run() method will be called when the thread is ready to
execute. |
void setName(String) |
Instance method |
Changes the name of the thread to
the given name in the
argument. |
void setPriority(int) |
Instance method |
Sets the priority of the thread to
the given argument value |
void
sleep(long), void sleep(long,
int) |
Overloaded
static
methods |
Makes the current thread sleep for
given milliseconds
(given in long) or for given milliseconds and nanoseconds
(given in long and int). |
void start() |
Instance method |
Starts the thread; JVM calls the
run() method of the
thread. |
String toString() |
Instance method |
Returns the string representation of
the thread; the string
has the thread’s name, priority, and its group. |
Method |
Method Type |
Short Description |
void
wait(),
void
wait(long), void wait(long,
int) |
Overloaded
instance
methods |
The current thread should have
acquired a lock on this
object before calling any of the wait methods.
If wait() is called, the thread waits infinitely until some
other thread notifies (by calling the notify()/notifyAll()
method) for this lock.
The method wait(long) takes milliseconds as an argument.
The thread waits till it is notified or the timeout happens.
The wait(long, int) method is similar to wait(long) and
additionally takes nanoseconds as an argument. |
void notify() |
Instance method |
The current thread should have
acquired a lock on this
object before calling notify(). The JVM chooses a single
thread that is waiting on the lock and wakes it up. |
void notifyAll() |
Instance method |
The current thread should have
acquired a lock before
calling notifyAll(). The JVM wakes up all the threads
waiting on a lock. |
Basic ways to create thread?
- By extending the Thread class
You need to override the run() method when you want to extend the Thread class. If you don’t override the run() method, the default run() method from the Thread class will be called, which does nothing.
- By implementing the Runnable interface
The Thread class itself implements the Runnable interface. Instead of extending the Thread class, you can implement the Runnable interface. The Runnable interface declares a sole method, run()
Note: run() method should be declared as public void run().
Should you extend the Thread or implement the Runnable?
Since Java supports only single inheritance, if you extend from Thread, you cannot extend from any other class.
On the other hand, if you implement the Runnable interface, you can still extend some other class. So, many Java experts suggest that it is better to implement the Runnable interface unless there are some strong reasons to extend the Thread class.
Why we call start() methods instead of run() method?
First we see one example:
public class MyFirstThread extends Thread {
public void run() {
System.out.println("In run method; thread name is: " +
Thread.currentThread().getName());
}
public static void main(String args[]) throws Exception {
Thread myThread = new Thread(new MyFirstThread());
myThread.run(); // note run() instead of start() here
System.out.println("In main method; thread name is : " +
Thread.currentThread().getName());
}
}
Output :
In run method; thread name is: main
In main method; thread name is : main
The start() method starts the execution of the new thread and calls the run() method. The start() method returns immediately and the new thread normally continues until the run() method returns.
On the other hand if you can run() directly, it simply executes as part of the calling thread.It does not execute as a thread: it doesn't get scheduled and get called by the JVM
That is why the getName() method in the run() method returns “main” instead of “Thread-0.” When you call the start() method, the thread gets scheduled and the run() method is invoked by the JVM when it is time to execute that thread.
**Never call the run() method directly for invoking a thread. Use the start() method and leave it to the JVM to implicitly invoke the run() method. Calling the run() method directly instead of calling start() is a mistake and is fairly common bug.
Thread Name, Priority, and Group
Every thread has a name, which you can used to identify the thread. If you do not give a name explicitly, a thread will get a default name. The priority can vary from 1, the lowest, to 10, the highest
class SimpleThread {
public static void main(String []s) {
Thread t = new Thread();
System.out.println(t);
}
}
This program prints the following:
Thread[Thread-0,5,main]
Explanation: default name Thread-0,The default priority is 5,default thread group is main.
You can change the same like this
t.setName("SimpleThread");
t.setPriority(9);
The States of a Thread
A thread has various states during its lifetime.You’ll see three thread states—new, runnable and terminated—which are applicable to almost all threads
How to access thread state in a program?
- Thread.State enumeration
- getState() instance method
Two States in “Runnable” State
- ready state
- running state
A thread is in the ready state when it is waiting for the OS to run it in the processor. When the OS actually runs it in the processor, it is in the running state
Basic Thread state example:
class BasicThreadStates extends Thread {
public static void main(String []s) throws Exception {
Thread t=new Thread(new BasicThreadStates());
System.out.println("Just after creating thread; \n" +"The thread state is: " + t.getState());
t.start();
System.out.println("Just after calling t.start(); \n" +"The thread state is: " + t.getState());
t.join();
System.out.println("Just after main calling t.join(); \n" +"The thread state is: " + t.getState());
}
}
timed_waiting and blocked States
Example:
class SleepyThread extends Thread {
public void run() {
synchronized(SleepyThread.class) {
try {
Thread.sleep(1000);
}
catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
class MoreThreadStates {
public static void main(String []s) {
Thread t1=new SleepyThread();
Thread t2 =new SleepyThread();
t1.start();
t2.start();
System.out.println(t1.getName() +": I'm in state " + t1.getState());
System.out.println(t2.getName() +": I'm in state " + t2.getState());
}
}
Output will look like :
Thread-0: I'm in state TIMED_WAITING
Thread-1: I'm in state BLOCKED
Concurrent Access Problems
Threads share memory, and they can concurrently modify data. Since the modification can be done at the same time without safeguards, this can lead to unintuitive results.
Data Races or Race condition or Race hazard
When two or more threads are trying to access a variable and one of them wants to modify it, you get a problem known as a data race.
Let see with an example:
class Counter {
public static long count = 0;
}
class UseCounter implements Runnable {
public void increment() {
Counter.count++;
System.out.print(Counter.count + " ");
}
public void run() {
increment();
increment();
increment();
}
}
// This class creates three threads
public class DataRace {
public static void main(String args[]) {
UseCounter c = new UseCounter();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
t3.start();
}
}
when you run this program, it does print nine integer values, but the output looks like
3 3 5 6 3 7 8 4 9
The expression Counter.count++ is a write operation, and the next System.out.print statement has a read operation for Counter.count. When the three threads execute, each of them has a local copy of the value Counter.count and when they update the counter with Counter.count++, they need not immediately reflect that value in the main memory .In the next read operation of Counter.count, the local value of Counter.count is printed.
To avoid this problem, you need to ensure that a single thread does the write and read operations together.The section of code that is commonly accessed and modified by more than one thread is known as critical section.
Now Synchronization will come into picture.
Thread Synchronization
Java has a keyword, synchronized, that helps in thread synchronization
Use of synchronized keyword:
- synchronized blocks
- synchronized methods
synchronized blocksIn synchronized blocks, you use the synchronized keyword for a reference variable and follow it by a block of code. A thread has to acquire a lock on the synchronized variable to enter the block; when the execution of the block completes, the thread releases the lock.
synchronized(this) {
// code segment guarded by the mutex lock
}
What if an exception gets thrown inside the synchronized block? Will the lock get released?
Yes, no matter whether the block is executed fully or an exception is thrown, the lock will be automatically released by the JVM.
With synchronized blocks, you can acquire a lock on a reference variable only. If you use a primitive type, you will get a compiler error.
int i = 10;
synchronized(i) { /* block of code here*/}
synchronized methodsIn entire method can be declared synchronized. In that case, when the method declared as synchronized is called, a lock is obtained on the object on which the method is called, and it is released when the method returns to the caller.
A synchronized method is equivalent to a synchronized block if you enclose the whole method body in a synchronized(this) block.
Synchronized Blocks vs. Synchronized Methods
If you want to acquire a lock on an object for only a small block of code and not the whole method, then synchronized blocks are sufficient; using synchronized methods is overkill in that case.In general, it is better to acquire locks for small segments of code instead of locking methods unnecessarily, so synchronized blocks are useful there.
In synchronized blocks, you can explicitly provide the reference object on which you want to acquire a lock.
However, in the case of a synchronized method, you do not provide any explicit reference to acquire a lock on. A synchronized method acquires an implicit lock on the this reference.
Deadlocks
Obtaining and using locks is tricky, and it can lead to lots of problems. One of the difficult problems is known as a deadlock.
A deadlock arises when locking threads result in a situation where they cannot proceed and thus wait indefinitely for others to terminate.Say, one thread acquires a lock on resource r1 and waits to acquire another on resource r2. At the same time, say there is another thread that has already acquired r2 and is waiting to obtain a lock on r1. Neither of the threads can proceed until the other one releases the lock, which never happens—so they are stuck in a deadlock.
Let's take an example from cricket game. In this, we have two resources one will hold the total numbers of balls thrown so far and other to hold numbers of runs scored so far.
/*
* Hold number of balls
*/
class Balls{
public static int balls=0;
}
/*
* Hold number of runs
*/
class Runs{
public static int runs=0;
}
class CricketCounter implements Runnable{
public void run() {
IncrementRunAfterBall();
IncrementBallAfterRun();
}
public void IncrementRunAfterBall(){
//since we are updating balls first
synchronized (Balls.class) {
System.out.println(Thread.currentThread().getName()+" waiting for run resources");
synchronized (Runs.class) {
Balls.balls++;
Runs.runs++;
}
}
}
public void IncrementBallAfterRun(){
//since we are updating Runs first
synchronized (Runs.class) {
System.out.println(Thread.currentThread().getName()+" waiting for ball resources");
synchronized (Balls.class) {
Runs.runs++;
Balls.balls++;
}
}
}
}
public class DeadlockDemo {
public static void main(String[] args) {
CricketCounter c= new CricketCounter();
Thread t1=new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
Sample Output
Thread-0 waiting for run resources
Thread-1 waiting for run resources
Thread-0 waiting for ball resources
From the above output, you can easily see that both the thread has already acquired the Balls resources and Thread-0 acquired the Runs resources and Thread-1 waiting for Runs resources that is already acquired by other thread-0.
When the threads t1 and t2 execute, they invoke the methods IncrementBallAfterRun and IncrementRunAfterBall. In these methods, locks are obtained in opposite order. It might happen that t1 acquires a lock on Runs.class and then waits to acquire a lock on Balls.class. Meanwhile, t2 might have acquired the Balls.class and now will be waiting to acquire a lock on the Runs.class. Therefore, this program can lead to a deadlock
Related Post:
Daemon Thread in Java
Thread communication with wait(), notify(), notifyAll()
Detail concept of class loader in Java
Interview related concept of Garbage Collection in java
If you know anyone who has started learning Java, why not help them out! Just share this post with them.Thanks for studying today!...