In concurrent programming, there are two basic units of execution:
- Processes
- Threads
In the Java programming language, concurrent programming is mostly concerned with threads.
A computer system normally has many active processes and threads. This is true even in systems that only have a single execution core, and thus only have one thread actually executing at any given moment. Processing time for a single core is shared among processes and threads through an OS feature called time slicing.
It's becoming more and more common for computer systems to have multiple processors on a single motherboard or processors with multiple execution cores. This greatly enhances a system's capacity for concurrent execution of processes and threads — but concurrency is possible even on simple systems, without multiple processors or execution cores.
Threads
Threads are the most basic ways of obtaining concurrency in Java
A thread is a unit of computation that always executes in context of a process. Thread always exists in a process. In fact, every process has at least 1 thread.
A thread is also called as light-weight processes, because time and resources taken to create a thread are fewer than creating a process.
Threads share the process's resources, including memory and open files. This makes for efficient, but potentially problematic, communication.
Multithreaded execution is an essential feature of the Java platform. Every application has at least one thread — or several, if you count "system" threads that do things like memory management and signal handling. But from the application programmer's point of view, you start with just one thread, called the _main _thread. This thread has the ability to create additional threads
Process
A process is a unit of resource allocation and protection. A process generally has a complete, private set of basic run-time resources; in particular, each process has its own memory space.
A thread is a thing that does the actual work, while process provides a container in which threads do the work. Process has a self-contained execution environment.
Anything inside a process has to be given special permissions to access anything outside the process. Thus, process protects things running inside it. To facilitate communication between processes, most operating systems supportInter Process Communication (IPC) resources, such as pipes and sockets. IPC is used not just for communication between processes on the same system, but processes on different systems.
Processes are often seen as synonymous with programs or applications. However, what the user sees as a single application may in fact be a set of cooperating processes.
Most implementations of the Java virtual machine run as a single process. A Java application can create additional processes using a
ProcessBuilder
object.
Under the hood, there are many more threads than the cores, so the underlying OS and JVM are responsible for adjudicating the access of threads to cores.
How Java Threads / Processes Communicate with Each Other
If threads within same process | If threads are in different processes |
---|---|
Since threads have the access to the same memory inside a process, they can communicate with each other using: 1) Shared Objects (as the shared objects reside in same address space. 2) Passing messages to each other via queues. | Since each process run in its own space, a process cannot access other processes' memory space. So, threads in a process cannot communicate with thread in other process. However, 2 processes can communicate with each other via inter-process communication through various means, like - Pipes, Sockets, CORBA messages etc. These proceses can be on same machine or in different machines. |
Threads and Java Runtime Data
Shared Accross Threads:
Threads share below memory areas:
- Method Area
- Heap
Because the heap and method area are shared by all threads, Java programs need to coordinate multi-threaded access to two kinds of data:
- Instance variables, which are stored on the heap
- Class variables, which are stored in the method area
Private for Each Thread:
However, each threads has its own "State", and the state is maintained by using:
- Stack
- Program Counter Registers (to keep a track of next instruction)
Programs never need to coordinate access to local variables, which reside on Java stacks, because data on the Java stack is private to the thread to which the Java stack belongs.
Ways of Providing Code to Java Threads
Method 1 - Extend the Thread class
- Create a subclass that
extends
Thread class. (java.lang.Thread) - Override the
run()
method. It contains the code to be executed by the thread. - Create an instance of subclass
- Call
start()
method on it.
public class MyThread extends Thread {
@Override
public void run() {
// Code to run
}
}
final MyThread t = new MyThread();
t.start();
Method 2 - Implement the Runnable interface
- Create a subclass that
implements
Runnable interface. (java.lang.Runnable) - Override the
run()
method. It contains the code to be executed by the thread. - Create an instance of your subclass.
- Create a thread, and pass the runnable to the thread as an argument.
- Call
start()
method on it.
public class MyThread implements Runnable {
@Override
public void run() {
// Code to run
}
}
final Runnable runnable = new MyThread();
final Thread t = new Thread(runnable);
t.start();
NOTE - java.lang.Thread constructor does not accept Callable as its parameter. Only Runnable is accepted.
Typically, implementing Runnable
than extending Thread
class is the preferred way. Reasons being:
Extending
Thread
class is usually not a good OO practice. Why? Because subclassing should be reserved for specialized versions of more general superclasses. So the only time it really makes sense (from an OO perspective) to extendThread
is when you have a more specialized version of aThread
class. In other words, because you have more specialized thread-specific behavior. Chances are, though, that the thread work you want is really just a job to be done by a thread. In that case, you should design a class that implements the Runnable interface, which also leaves your class free to extend some other class.Since a Java class can extend only 1 class, if your class extends
Thread
class, then there is no room left for some other class to extend. Implementing aRunnable
keeps the avenue open to extend any other class.If you implement Runnable, to have code run by a separate thread, you still need a Thread instance. But rather than combining both the thread and the job (the code in the
run()
method) into one class, you've split it into two classes—theThread
class for the thread-specific code and yourRunnable
implementation class for your job-that-should-be-run-by-a-thread code. (Another common way to think about this is that the Thread is the "worker," and the Runnable is the "job" to be done.)
Method 3 - Implement the Runnable interface, but using anonymous class (Variant of method # 2)
new Thread(new Runnable() {
@Override
public void run() {
// Code to run
}
}).start();
Method 4 - Java 8 specific, by using Lambda expression (Syntactic sugar for method # 3)
final Thread t = new Thread(() -> {
// Code to run
});
t.start();
Method 5 - Using Runnable / Callable with ExecutorService & Thread Pool
- Make an instance of ExecutorService (java.util.concurrent.ExecutorService)
- Submit either a Runnable or Callable to the service.
- This will return a Future object (for Runnable as well as Callable). We need to deal with Future object to process the data.
ExecutorService service = Executors.newSingleThreadExecutor();
Future future = service.submit(new Runnable() {
@Override
public void run() {
// Code to run
}
});
However, it cannot be said as the new way to create a Thread. It is because ExecutorService internally uses “ThreadFactory” class to create a new thread, which internally uses either first or second method. So we can say that there are only two ways to create threads, but there is a new way in Java 1.5 to invoke a thread but not to create a Thread.
If you create a thread using the no-arg constructor, the thread will call its own
run()
method when it's time to start working. That's exactly what you want when
you extendThread
, but when you useRunnable
, you need to tell the new thread to
use yourrun()
method rather than its own. The Runnable you pass to the Thread
constructor is called the target or the target Runnable.You can pass a single Runnable instance to multiple Thread objects so that the
same Runnable becomes the target of multiple threads, as follows:
public class TestThreads { public static void main (String[] args) { MyRunnable r = new MyRunnable(); Thread foo = new Thread(r); Thread bar = new Thread(r); Thread bat = new Thread(r); } }
Giving the same target to multiple threads means that several threads of execution will be running the very same job (and that the same job will be done multiple times).
The
Thread
class itself implementsRunnable
. (After all, it has arun()
method that we were overriding.) This means that you could pass a Thread to another
Thread's constructor:
Thread t = new Thread(new MyThread());
This is a bit silly, but it's legal. In this case, you really just need a
Runnnable
, and creating
a whole other Thread is overkill.
Passing Parameters to Java Thread
The signature or run()
does not accept any parameters. However, there are 2 ways to pass parameters to a thread:
- Create a named class. Pass the parameter to the constructor of named class and then access the same variable in
run()
. This applies to primitives as well as reference variables. - If using anonymous inner class, then making the local variable in scope as
final
.
Approach # 1 - Named class and constructor
public class MyThread implements Runnable {
int a;
int b;
public MyThread(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
int c = a + b;
System.out.println("Sum is: " + c);
}
}
// In main method
Runnable runnable = new MyThread();
Thread t = new Thread(runnable);
t.start();
Approach # 2 - Anonymous class and final local variable
// In main method
final int a = 10;
final int b = 20;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
int c = a + b;
System.out.println("Sum is: " + c);
}
});
t.start();
Why the local variables accessed by anonymous inner classes should be final OR effectively final?
The answer lies in how anonymous inner classes are actually implemented by the JVM.
Although it appears that methods in anonymous class can access local variables within the context in which the class exists, the methods in an anonymous class don't really have access to local variables and method parameters. Rather, when an object of the anonymous class is instantiated, copies of the local variables and method parameters referred to by the object's methods are stored as private instance variables in the object. These values are passed on to the object via the constructor. The methods in the object of the anonymous class merely access those hidden instance variables, and NOT the local variables.
Hence, if the local variable is not final, then there is always a risk of its value being changed AFTER the anonymous inner class has been instantiated. The instance of inner class would not have access to the changed value, and this could lead to incorrect behavior by the inner class's methods. So, the local variables accessed by inner class must be final or effectively final.
Effectively Final - A variable or parameter whose value is never changed after it is initialized is effectively final.
In the below program, since we are re-assigning the value variable 'a', the program will fail at compile time with error: "Local variable a defined in an enclosing scope must be final or effectively final" at line # 5.
We can fix the below code, either by:
- Making variable 'a' as final at line # 3, in which case, we need to remove line # 5.
- Removing line # 5, and marking nothing as final. In this case, variables 'a' and 'b' would be _effectively _final.
1. public class ThreadsTest { 2. public static void main(String args[]) { 3. int a = 10; 4. int b = 20; 5. a = 40; 6. Thread t = new Thread(new Runnable() { 7. @Override 8. public void run() { 9. int c = a + b; 10. System.out.println("Sum is: " + c); 11. } 12. }); 13. t.start(); 14. } 15. }
OCA/OCP Book Wisdom: When it comes to threads, very little is guaranteed
There are multiple "layers" involved when a thread is created and started:
System Libraries contains OS specific threading libraries.
The JVM, which gets its turn at the CPU by whatever scheduling mechanism the underlying OS uses, operates like a mini-OS and schedules its own threads, regardless of the underlying operating system. In some JVMs, the Java threads are actually mapped to native OS threads, however, every JVM implementation is free to implement this behavior. In fact, the most important concept to understand is: When it comes to threads, very little is guaranteed.
Don't make the mistake of designing your program to be dependent on a particular implementation of the JVM. As you'll learn a little later, different JVMs can run threads in profoundly different ways. For example, one JVM might be sure that all threads get their turn, with a fairly even amount of time allocated for each thread in a nice, happy, round-robin fashion. But in other JVMs, a thread might start running and then just hog the whole show, never stepping out so others can have a turn. If you test your application on the "nice turn-taking" JVM and you don't know what is and is not guaranteed in Java, then you might be in for a big shock when you run it under a JVM with a different thread-scheduling mechanism.
Passing same Runnable to multiple Threads
public class MultipleThreads {
public static void main(String[] args) {
Runnable r = new TestRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.setName("T-1");
t2.setName("T-2");
t3.setName("T-3");
t1.start();
t2.start();
t3.start();
}
}
class TestRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println("Currently Executing Thread is: " + Thread.currentThread().getName()
+ " and current Value of i: " + i);
}
}
}
The above code prooves that:
- Nothing is guaranteed in the preceding code except:
- Each thread will start, and each thread will run to completion.
Within each thread, things will happen in a predictable order. But the actions of
different threads can mix in unpredictable ways. If you run the program multiple
times or on multiple machines, you may see different output and the program behavior will be unpredictable. The reason - Its upto the scheduler, and we don't control the scheduler.
User Threads and Daemon Threads
The threads that are created by your application are called as "user" threads. You and the operating system can create a second kind of thread called a "daemon" thread. The difference between these two types of threads [user and daemon] is that the JVM exits an application only when all user threads are complete—the JVM doesn't care about letting daemon threads complete, so once all user threads are complete, the JVM will shut down, regardless of the state of any daemon threads. However, this will effectively kill all the daemon threads as well!
Thread States - New, Alive, Dead
When you create a new instance of thread, by writing below code:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// Job to be done by Thread
}
});
So now, you've made yourself a Thread instance, and it knows which run()
method to call. But nothing is happening yet. At this point, all we've got is a plain old Java object 't' of type Thread
, which is created on heap. It is not yet a thread of execution. To get an actual thread - a new call stack - we still have to start the thread.
t.start();
At this moment, a separate stack is created for the thread, and the thread starts its execution.
New State - When a thread has been instantiated but not started (in other words, the start()
method has not been invoked on the Thread instance), the thread is said to be in the new _state. At this stage, the thread is not yet considered alive. The _new state means you have a Thread
object but you don't yet have a true thread of execution.
Alive State - Once the start()
method is called, the thread is considered alive (even though the run()
method may not have actually started executing yet). So what happens after you call start()? The good stuff:
- A new thread of execution starts (with a new call stack).
- The thread moves from the new state to the runnable state.
- When the thread gets a chance to execute, its target
run()
method will run.
Dead State - A thread is considered dead (no longer alive) after the run()
method completes, either by:
- Successfully completing its execution, OR
Throwing an exception
When a thread completes its
run()
method, the thread ceases to be a thread of execution. The stack for that thread dissolves, and the thread is considered dead. It's still aThread
object, just not a thread of execution. So if you've got a reference to aThread
instance, then even when that Thread instance is no longer a thread of execution, you can still call methods on the Thread instance, just like any other Java object. What you can't do, though, is callstart()
again. Once a thread has been started, it can never be started again. If you have a reference to a Thread and you callstart()
, it's started. If you callstart()
a second time, it will cause an exception (an IllegalThreadStateException, which is a kind of RuntimeException)
The isAlive()
method is the best way to determine if a thread has been started but has not yet completed its run()
method. (Note: The getState()
method is very useful for debugging).
There's nothing special about the run()
method as far as Java is concerned. Like main()
, it just happens to be the name (and signature) of the method that the new thread knows to invoke. So if you see code that calls the run()
method on a Runnable (or even on a Thread
instance), that's perfectly legal. But it doesn't mean the run()
method will run in a separate thread! Calling a run()
method directly just means you're invoking a method from whatever thread is currently executing, and the run()
method goes onto the current call stack rather than at the beginning of a new call stack. The following code does not start a new thread of execution:
Thread t = new Thread();
t.run(); // Legal, but does not start a new thread
Picture of main thread, a method in main thread and a thread spawned by the method
Thread Scheduler
The thread scheduler is the part of the JVM (although most JVMs map Java threads directly to native threads on the underlying OS) that decides which thread should run at any given moment, and also takes threads out of the run state.
Assuming a single processor machine, only one thread can actually run at a time. Only one stack can ever be executing at one time. And it's the thread scheduler that decides which thread - of all that are eligible - will actually run. When we say eligible, we really mean in the runnable state. i.e. the thread has been started! Any thread in the runnable state can be chosen by the scheduler to be the one and only running thread. If a thread is not in a runnable state, then it cannot be chosen to be the currently running thread.
And just so we're clear about how little is guaranteed here: The order in which runnable threads are chosen to run is not guaranteed. Although queue behavior is typical, it isn't guaranteed. Queue behavior means that when a thread has finished with its "turn," it moves to the end of the line of the runnable pool and waits until it eventually gets to the front of the line, where it can be chosen again. In fact, we call it a runnable pool, rather than a runnable queue, to help reinforce the fact that threads aren't all lined up in some guaranteed order. Although we don't control the thread scheduler (we can't, for example, tell a specific thread to run), we can sometimes influence it. The following methods give us some tools for influencing the scheduler. Just don't ever mistake influence for control.
Methods from java.lang.Thread class | Methods from java.lang.Object class |
---|---|
public static void sleep(long ms) | public final void wait() |
public static void sleep(long ms, int nanos) | public final void wait(long timeout) |
public static void yeild() | public final void wait(long timeout, int ms) |
public void join() | public final void notify() |
public void join(long ms) | public final void notifyAll() |
public void join(long ms, int nanos) | |
public void setPriority(int newPriority) |
How to know which Thread is executing the code in
Runnable
'srun()
method?The target Runnable instance doesn't have a reference to the Thread instance. So, to get a reference to the currently executing thread, we can use the static method
Thread.currentThread()
method. This method returns a reference to currently executing thread object.
Thread Lifecycle

Thread State | Description |
---|---|
NEW | A thread that has been created but hasn't started it. It is just a Thread object on heap, not a thread-of-execution. |
READY / RUNNABLE | t.start() method will move a thread into RUNNABLE state. It will go into runnable pool, and waiting for to be picked by the scheduler to run |
RUNNING | This is where the action happens. The OS is running the thread, that means the thread is now a thread of execution and we have a separate stack for the executing thread. |
WAITING | The thread is waiting indefinitely for some another thread to complete its (other thread's) actions. It will go into pool of runnables once it gets notified using notify() or notifyAll() |
SLEEPING (TIMED_WAITING) | The thread is waiting for some specified time for some another thread to complete its (other thread's) actions. It will go into pool of runnables once the timeout elapses. |
BLOCKED | The thread is waiting for a resource, such as an IO operation or acquaring a lock. From this state, thread will go into pool of runnables once the resource is acquired. |
TERMINATED | A thread whose run() method has finished its execution. It is no longer a thread of execution, its stack has disappeared, but it is still a "thread" object lying on heap. |
Calling start()
on a new thread instance implicitly calls its run()
, which transitions its state from NEW to RUNNABLE. A thread in the RUNNABLE state is all set to be executed. It’s just waiting to be chosen by the thread scheduler so that it gets the processor time. Thread scheduling is specific to the underlying OS on every system. As a programmer, you can’t control or determine when a particular thread transitions from the RUNNABLE state to the RUNNING state, and when it actually gets to execute. A thread scheduler follows various scheduling mechanisms to utilize a processor efficiently, and also to give a fair share of processor time to each thread. It might suspend a running thread to give way to other RUNNABLE threads and it might execute it later.
A running thread enters the TIMED_WAITING / SLEEPING state, when it might need to wait for a specified interval of time, before it can resume its execution. It happens when sleep(int)
,join(int)
, or wait(int)
is called on a running thread. On completion of the elapsed interval, a thread enters the queue eligible to be scheduled by the thread scheduler. Whenjoin()
or wait()
is called on a running thread, it transitions to the WAITING state. It can change back to the RUNNABLE state when notify()
or notifyAll()
is called. A RUNNABLE thread might enter the BLOCKED state when it’s waiting for other system resources like network connections or to acquire an object lock to execute a synchronized method or code block. Depending on whether the thread is able to acquire the monitor lock or resources, it returns back to the RUNNABLE state.
With the successful completion of run()
, a thread enters the TERMINATED state. A thread might transition from any state to the TERMINATED state due to an exception.
The methods suspend(), resume() and stop() are deprecated and should not be used.
The important point is that one thread does not tell another thread to block. Some methods may look like they tell
another thread to block, but they don't. If you have a reference 't' to another thread, you can write something like this:t.sleep(); OR t.yield();
But those are actually static methods of the Thread class - they don't affect the instance 't'; instead, they are defined to always affect the thread that's currently executing. (This is a good example of why it's a bad idea to use an instance variable to access a static method—it's misleading)
Methods of class java.lang.Thread
start()
- This method can only be called when the thread is in NEW state. Calling start()
from any other state will result in IllegalThreadStateException
. start()
creates a new thread of execution, which executes run()
.
Pausing Thread Execution - yield()
, sleep()
, join()
yield()