Other Parts of This Series:


Java Thread Programming (Photo Credit: Medium)

Java Thread Programming (Photo Credit: Medium)

N.B: Please read the following concepts for better understanding before continue reading this article.

β˜› Threads, Asynchronicity, Concurrency, Parallelism


What is a Thread?

  • A thread = a lightweight unit of execution within a process.
  • A Java process (JVM instance) always starts with main thread.

What is Multi-Threading?

  • Multithreading means executing multiple threads concurrently in a single program.
  • Normally Java programs run on single thread called main thread.
  • But you can create more threads to run tasks concurrently (not necessarily in parallel, depends on CPU cores & scheduling).

πŸ‘‰ Benefit: Run multiple things at once (e.g., UI stays responsive while downloading data).

Benefits of Multithreading:

  • Better CPU utilization
  • Faster execution of independent tasks
  • Efficient resource sharing
  • Makes real-time apps responsive
  • Handles multiple tasks simultaneously (like UI + background download)
  • Essential for concurrent systems, gaming, real-time applications

Thread Creation Process

There are several ways to create thread in Java. Some of them are below:

  1. Extending Thread:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MyThread extends Thread {
    public void run() {
        System.out.println("Running in: " + Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start(); // start() creates a new thread and calls run()
    }
}
  1. Implementing Runnable / Callable:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MyTask implements Runnable {
    public void run() {
        System.out.println("Task executed by: " + Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyTask());
        t.start();
    }
}

Prefer Runnable if your class needs to extend another class.

  1. With Lambda (modern style):
1
2
3
4
Thread t = new Thread(() -> {
    System.out.println("Running with lambda!");
});
t.start();

Thread Lifecycle

A thread goes through states:

  • NEW β†’ after creation (new Thread(…)).
  • RUNNABLE β†’ after calling start().
  • RUNNING β†’ when CPU picks it.
  • WAITING / TIMED_WAITING / BLOCKED β†’ when waiting or locked.
  • TERMINATED β†’ after finishing run().

πŸ‘‰ We can inspect state with t.getState().

Common Java Thread Methods

  1. start()

Starts a new thread and internally calls run().

1
new Thread(() -> System.out.println("Hello")).start();
  1. run()

The code executed when the thread runs (don’t call it directly β€” use start()).

1
2
3
class MyThread extends Thread {
  public void run() { System.out.println("Running..."); }
}
  1. sleep(ms)

Pause current thread for given milliseconds.

1
Thread.sleep(1000); // 1 second pause
  1. join()

Wait for another thread to finish.

1
t.join(); // main waits until t finishes
  1. currentThread()

Returns the currently executing thread.

1
System.out.println(Thread.currentThread().getName());
  1. getName() / setName()

Get or set thread name.

1
2
t.setName("Worker-1");
System.out.println(t.getName());
  1. getId()

Returns unique thread ID.

1
System.out.println(t.getId());
  1. getState()

Returns the state (NEW, RUNNABLE, BLOCKED, WAITING, etc.).

1
System.out.println(t.getState());
  1. isAlive()

Checks if thread is still running.

1
if(t.isAlive()) System.out.println("Still working...");
  1. yield()

Hints scheduler to let other threads run.

1
Thread.yield();
  1. interrupt()

Sends an interrupt signal to a thread.

1
t.interrupt();
  1. isInterrupted() / interrupted()

Check if a thread was interrupted.

1
if(Thread.currentThread().isInterrupted()) break;
  1. setPriority(int) / getPriority()

Suggest CPU scheduling priority (1=MIN, 5=NORM, 10=MAX).

1
t.setPriority(Thread.MAX_PRIORITY);
  1. setDaemon(boolean)

Mark a thread as daemon (ends when all user threads finish).

1
t.setDaemon(true);
  1. activeCount()

Returns number of active threads in current group.

1
System.out.println(Thread.activeCount());

⚠️ Best practice: use interrupt() + check Thread.interrupted() instead of stop() (unsafe).


Shared Data Problem (Race Conditions)

Threads share memory β†’ risk of corruption if multiple threads write at same time.

Example (bad):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Counter {
    int count = 0;
    void increment() { count++; }
}

Counter c = new Counter();
Thread t1 = new Thread(() -> { for(int i=0;i<1000;i++) c.increment(); });
Thread t2 = new Thread(() -> { for(int i=0;i<1000;i++) c.increment(); });
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println(c.count); // not always 2000!

Synchronization (Solution)

  1. synchronized keyword: Only one thread can enter the method at a time.

Example:

1
2
3
4
class Counter {
    int count = 0;
    synchronized void increment() { count++; }
}
  1. Locks: More flexible than synchronized.

Example:

1
2
3
4
ReentrantLock lock = new ReentrantLock();
lock.lock();
try { /* critical section */ }
finally { lock.unlock(); }
  1. Volatile: Ensures visibility of variable changes across threads, but doesn’t guarantee atomicity.

Example:

1
volatile boolean running = true;

Communication Between Threads

  • wait() / notify()
1
2
3
4
synchronized(obj) {
    obj.wait();    // wait until notified
    obj.notify();  // wake up a waiting thread
}
  • BlockingQueue (preferred!)
1
2
3
BlockingQueue<String> q = new LinkedBlockingQueue<>();
new Thread(() -> { try { q.put("data"); } catch(Exception e){} }).start();
System.out.println(q.take());

Executor Framework (Java 5+)

Manages thread pools automatically. Better than creating threads manually.

Example:

1
2
3
4
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Task 1"));
pool.submit(() -> System.out.println("Task 2"));
pool.shutdown();

Futures & CompletableFuture

Async programming with callbacks.

Example:

1
2
3
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);

More Modern Java Concurrency

  1. Virtual Threads (Java 21+)

Thousands of lightweight threads β†’ ideal for servers.

Example:

1
2
3
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> System.out.println("Hello from virtual thread"));
}
  1. Scoped Values (Java 25)

Safer alternative to ThreadLocal.

Example:

1
2
3
4
ScopedValue<String> USER = ScopedValue.newInstance();
ScopedValue.where(USER, "Rasel").run(() ->
    System.out.println("Running as " + USER.get())
);
  1. Structured Concurrency (Java 25, preview)

Treat related tasks as one unit, easier cancellation & error handling.

Example:

1
2
3
4
5
6
try (var scope = StructuredTaskScope.open()) {
    var f1 = scope.fork(() -> "Task1");
    var f2 = scope.fork(() -> "Task2");
    scope.join();
    System.out.println(f1.get() + ", " + f2.get());
}