Threads

  • A process can be constituted of multiple threads
  • Each thread has stack and variables and shares memory with other threads
  • Thread that belong to the same process have access to process data and code
  • Communicating between threads of different processes is difficult
  • Creating and Switching between multiple threads is easier compared to multiple processes
  • Status:
    • NEW
    • RUNNABLE
      • BLOCKED
    • TERMINATED
graph LR
NEW -->|Started| RUNNABLE
RUNNABLE -->|Waiting| BLOCKED
BLOCKED -->|Resume| RUNNABLE
RUNNABLE -->|Finished| TERMINATED

Java Thread

  • There are two ways to do it:
    1. Thread
    2. Runnable
  • States (by Thread.State)
    • NEW — Not Started
    • RUNNABLE — executing or waiting for resources
      • WAITING
      • TIMED_WAITING
      • BLOCKED — waiting for monitor lock
    • TERMINATED
stateDiagram
[*] --> NEW
NEW --> RUNNABLE

RUNNABLE --> WAITING
WAITING --> RUNNABLE
RUNNABLE --> TIMED_WAITING
TIMED_WAITING --> RUNNABLE
RUNNABLE --> BLOCKED
BLOCKED --> RUNNABLE

RUNNABLE --> TERMINATED

TERMINATED --> [*]

  • NEW
    • Not Started
  • RUNNABLE
    • executing or waiting for resources from the operating system such as processor
    • Running is just a sub-state of Runnable when scheduler has selected it
  • WAITING
    • waiting state because of:
      • Object.wait with no timeout
        • waiting for another thread to call Object.notify() or Object.notifyAll() on that object.
      • Thread.join with no timeout
        • waiting for a specified thread to terminate.
      • LockSupport.park
  • TIMED_WAITING
  • BLOCKED
    • waiting for monitor lock
  • TERMINATED
    • The thread has completed execution

Thread class

  • Thread class can be extended to create threads
  • You need to override run() method and one way to remember is that start() is a special method which calls native method implementation and hence should not be overridden
  • Cons
    • You cannot extend any more classes since only one class can be extended in java
    • When we extend Thread class, each of our thread creates unique object and associate with it
    • While if we use Runnable, it shares the same object to multiple threads
public class ThreadExample extends Thread {
	
	@Override
	public void run() {
		int i = 1;
		while (i <= 100) {
			System.out.println(i + " " + this.getName());
			i++;
		}
	}
}
  • and running it in main():
ThreadExample thread1 = new ThreadExample();
thread1.setName("First Thread");
thread1.start(); // starts the run() method
 
ThreadExample thread2 = new ThreadExample();
thread2.setName("Second Thread");
thread2.start(); // starts the run() method

Runnable + Thread Constructor

  • It is the preferred method
  • Runnable is a functional interface
  • Can be used as lambda
// taken from JDK 17
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • Creating Runnable:
    • Using implements
    • Using lambda
class RunnableExample implements Runnable {
    @Override
	public void run() {
		int i = 1;
		while (i <= 10) {
			System.out.println(i + " " + Thread.currentThread().getName());
			i++;
		}
	}
}
 
public class Test {
    public static void main(String[] args) {
        // By implementing Runnable
        Thread thread1 = new Thread(new RunnableExample());
        thread1.start(); // starts the run() method
 
        // By using lambda
        Thread thread2 = new Thread(() -> {
            int i = 1;
            while (i <= 10) {
                System.out.println(i + " " + Thread.currentThread().getName());
                i++;
            }
        });
        thread2.start(); // starts the run() method
    }
}

Thread methods

  • Static:
    • currentThread() — return current Thread instance
    • activeCount() — live thread count in ThreadGroup
    • holdsLock(Obj obj)
    • sleep()
    • yield()
    • interrupted()
  • Actions:
    • join()
    • interrupt()
    • run()
    • start()
  • Info:
    • getState()
    • getName() / setName()
    • getPriority() / setPriority()
    • isAlive()
    • isDaemon() / setDaemon()
    • isInterrupted()
    • isVirtual()
    • threadId()

run() vs start()

  • You should always start thread using start() method
  • If you use run() then the thread will be executed in the current thread, which is no different than calling regular method
  • When you use start() method, internally it calls start0() method which is a native implementation which creates a new thread and calls run() internally

interrupt() vs stop()

sleep()

  • Thread.sleep() causes current thread to suspend execution for a specified period
  • These sleep times are not guaranteed to be precise, because they are limited by the facilities provided by the underlying OS
  • Hence, you cannot assume that invoking sleep will suspend the thread for precisely the time period specified.
  • The sleep period can be terminated by interrupts, and method also throws checked exception for InterruptedException if it occurs
  • It puts current thread into TIMED_WAITING state
    • RUNNABLE TIMED_WAITING RUNNABLE

join()

  • t.join() causes the current thread to pause execution until t’s thread terminates
  • Like sleep()join() responds to an interrupt by exiting with an InterruptedException
  • It puts current thread into WAITING/TIMED_WAITING until target thread terminates
    • Current Thread: RUNNABLE WAITING/TIMED_WAITING RUNNABLE
    • Target Thread: RUNNABLE TERMINATED
  • how is it different from using CountDownLatch to wait for threads to complete?

isAlive()

  • Tests if this thread is alive. A thread is alive if it has been started and has not yet died.

setDaemon()

  • Marks this thread as daemon
  • setDaemon() must be called before calling thread.start() else IllegalThreadStateException will be thrown

Daemon Thread and setDaemon()

  • It is specific to java
  • Non-Daemon threads are also called User threads or Normal Threads
  • When the JVM starts up, all the threads it creates are daemon threads except the main thread
  • A new thread by default inherits daemon status from parent
  • Execution
    • Think of it as the code is executing on the JVM, if all the non-daemon threads exits, JVM exits too
    • When all non-daemon threads finish, the JVM initiates an orderly shutdown, and any remaining daemon threads are abandoned
    • If we run non-daemon threads, then they all must exit themselves in order to exit the JVM, or otherwise JVM will not exit and it will be stuck forever
    • Hence, make sure if you make a daemon thread, then stopping it abruptly should not corrupt or cause any negative side effects
  • Never use daemon thread for I/O related tasks
  • Generally Daemon threads are used for background activities supporting non-daemon threads
  • Example daemon thread in JVM:
    • Garbage collector

Thread Priority

  • Range: 1-10
    • Internally Hardcoded MIN_PRIORITY = 1 and MAX_PRIORITY = 10)
  • Default: 5
    • Internal: NORM_PRIORITY = 5
  • CPU favors (not guarantees) to run higher priority thread to run first
  • setPriority() will throw IllegalArgumentException if new priority is outside the range
  • Avoid using thread priorities as they increase platform independence and can cause liveness problems
  • Most concurrent applications can use default priority for all threads

ThreadGroup

  • A thread group represents a set of threads. In addition, a thread group can also include other thread groups.
  • The thread groups form a tree in which every thread group except the initial thread group has a parent
  • A thread is allowed to access information about its own thread group, but not to access information about its thread group’s parent thread group or any other thread groups

Parent-Child Threads

Prefer Reactive Programming

  • Prefer reactive programming to increase throughput
  • Reactive programming allows different threads to execute for different steps
  • In reactive endpoints
    • one thread will accept request
    • the request is transferred to another thread to execute business logic
  • In non-reactive endpoints
    • one thread will accept request as well as execute business logic
  • example reactive frameworks and libraries
    • JAX-RS
    • WebFlux
    • Vert.x
    • RxJava

Logging

  • Logging Increase throughput and latency
  • Piping the logs to file is faster than writing to stdout

Fork and Join in Threads

  • Java does not have posix fork() system call
  • It has ForkJoinPool which is different from Posix fork()
  • pthread_join is also different compared to thread.join() in java
    • Don’t see any info in internet