Multithreading

Components

Components involved in Multithreading in Java:

Threads

  • One Java thread corresponds to one CPU thread.

Life Cycle

Thread Scheduler

Task executes for a predefined slice of time and then re-enters pool of ready tasks. Next task decided based on priority and other factors.

Uses Executions#Pre-emptive Scheduling and Executions#Time Slicing.

Managing Threads and Code involving Threads

Creating/Defining Threads

Two ways-

  1. extend Thread class
  2. implement Runnable interface

Implement the run() method -> this is the code which will get executed when the thread runs.

Executing Threads

Triggering Thread Execution

  • Call start() method -

The part after start method call is executed immediately. If you want to execute some part of the code after the thread execution, use join() method.

Thread thread = new ApnaThread();

thread.start();

//this part gets executed immediately after start() is called - while the thread is running

thread.join();

//this part gets executed after the thread execution is completed.

Managing Thread Execution

Methods for thread synchronisation
Prioritising Threads

In Thread class:

  • NORM_PRIORITY = 5 (Default priority)
  • MAX_PRIORITY = 10
  • MIN_PRIORITY = 1
  • getPriority(): Returns priority of thread
  • setPriority(int): changes priority of a thread

How to make a block of code thread safe?

Multitasking#Thread Safety

  1. Thread local fields

    • Defining private fields in Thread classes
    • Assigning ThreadLocal instances to a field

    Like normal class fields but each thread accesses them via a setter-getter and gets independently initialized copy of the field so that each thread has its own state

  2. Synchronized Collections

  3. Concurrent Collections

    • Concurrent collections achieve thread safety by dividing their data into segments and acquiring locks on different segments.
    • Better performance
  4. Atomic objects

  5. Synchronized Methods

  6. Synchronized statements

  7. Other objects as lock

  8. Volatile fields

  9. Reentrant locks

  10. Read/write locks

Volatile and Atomic

volatile is used when there are two threads store and perform operations on same variable. In such case, both threads store the variable in different caches. This results in inaccuracy and inconsistency. Thus, the volatile keyword lets lang.java.jre.jvm know that this is variable is going to be changed and it is then stored in the common cache.

AtomicInteger, AtomicLong and other such classes are used in a similar situation to above, where the threads are accessing and performing some operation on a variable. If we don't define the variable as Atomic, it might happen that the second thread accesses the variable before the first thread is done performing operation on it. This causes inconsistency in the data. The AtomicInteger makes the integer thread safe by not letting any other thread access until one thread is done.

Thread Pool and ExecutorService

ExecutorService is an interface which allows us to execute tasks on thread asynchronously

Instantiation using Executors Factory class

Instance can be retrieved from Executors factory class using different factory methods based on the number and types of threads needed.

Following are the different types of thread pools/executors:

SingleThreadedExecutor
ExecutorService es = Executors.newSingleThreadExecutor();
FixedThreadPool
ExecutorService es = Executors.newFixedThreadPool(10);
ScheduledThreadPool

In scheduled thread pool, we can schedule tasks of the threads.

ExecutorService es = Executors.newScheduledThreadPool(10);
CachedThreadPool

???

If you use ExecutorService to initialise a thread pool. Multithreading-threadpool

  • Threads initialized will pick up tasks from the queue of tasks given to the executor service
  • Since multiple threads are accessing this queue simultaneously, executor service uses a Collections Framework#Concurrent Collections.

Thread Pool#Optimal Pool Size

💡 Use Runtime class to get the core count and initialise executor service with that.

int coreCount = Runtime.getRuntime().availableProcessors();

Executing Threads


execute(Runnable task)
submit(Runnable task) / submit(Callable<T> task)
invokeAny(Collection<? extends Callable<T>> tasks)
invokeAll(Collection<? extends Callable<T>> tasks);

Shutting down

We can use the following methods after we're done. If we don't, the threads will keep running and JVM won't shut down.

  • shutdown() method
  • shutdownNow() method
  • awaitTermination() method

Deadlocks

Detection

Deadlocks are diffcult to detect as there are multiple locks and threads in Java which run even without us specifying.

Detection using thread dumps

Use either of the following commands to ask the JVM to print info of all the threads running in the application.

Take the process ID of your application and run the following -

  • kill -3 12704 (where 12704 is the process ID)
  • jstack 11475 > ./out.txt (will put that log in the specified file) JVM will detect the deadlock itself and mention it.
Detection using Java MXBean

MXBean is an API which you can run constantly in the background which can detect and tell you when the application goes into a deadlock state.


private static void detectDeadlock(){

ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();

	long[] threadIds = threadBean.findDeadlockedThreads();
	boolean deadLock = threadIds != null && threadIds.length > 0;
	System.out.println("Deadlocks found: " + deadLock);
}

Prevention

  • Include timeout for your locks.
  • Have a global order for locks
  • Avoiding unnecessary and nested locks altogether
  • Joining the threads

References

© 2025 All rights reservedBuilt with Flowershow Cloud

Built with LogoFlowershow Cloud