Thread Creation in Java

Java Thread Creation: Traditional Threads vs ExecutorService vs Virtual Threads

Java Thread Creation Has Changed (And Most Developers Haven’t Noticed)

For years, we’ve been taught:

👉 Extend Thread
👉 Implement Runnable
👉 Use ExecutorService

But now?

Java has introduced Virtual Threads (Project Loom).

And this changes everything about how we think about concurrency.

Let’s not just learn syntax.

Let’s trace execution like we’re debugging a production system.


The Base: One Runnable, Multiple Execution Strategies

We are NOT going to create separate logic per thread type.

Instead:

👉 One task (BatchJob)
👉 Multiple execution strategies

This is how real systems are designed.

Base Classes (Given)

package org.code2java;

public class BatchJob implements Runnable {
    private final String jobName;

    public BatchJob(String jobName) {
        this.jobName = jobName;
    }

    @Override
    public void run() {
        System.out.println("Hello from a batch job: " + jobName);
        InitiateJob job = new InitiateJob();
        job.executeJob(jobName);
    }
}
package org.code2java;

import java.util.HashMap;
import java.util.Map;

public class InitiateJob {

    public static Map<String, String> jobParameters = new HashMap<>();

    public void executeJob(String jobName) {
        System.out.println("Initiating batch job : " + jobName);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        jobParameters.put(jobName, "Completed");
        System.out.println("Batch job " + jobName + " completed successfully.");
    }

    public String getJobStatus(String jobName) {
        return jobParameters.get(jobName);
    }
}

Now the Real Question

👉 Same Runnable
👉 Different execution models

What actually changes?

Let’s explore.


1. Traditional Thread (Classic OS Thread)

package org.code2java;

public class TraditionalThread {

    public static void main(String[] args) {

        Runnable job1 = new BatchJob("Job-1");
        Runnable job2 = new BatchJob("Job-2");

        Thread t1 = new Thread(job1);
        Thread t2 = new Thread(job2);

        t1.start();
        t2.start();
    }
}

What happens internally?

👉 Each Thread = 1 OS thread
👉 Heavy resource allocation
👉 Limited scalability

Problem ⚠️

👉 1000 threads = 1000 OS threads
👉 Memory + context switching explosion


2. ExecutorService (Thread Pool Model)

package org.code2java;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyExecutorServiceThread {

    public static void main(String[] args) {

        ExecutorService executor = Executors.newFixedThreadPool(2);

        executor.submit(new BatchJob("Job-1"));
        executor.submit(new BatchJob("Job-2"));
        executor.submit(new BatchJob("Job-3"));

        executor.shutdown();
    }
}

Internal Working

👉 Tasks → Queue
👉 Fixed threads → reuse
👉 Controlled concurrency

Why this works better

👉 Thread reuse
👉 Reduced overhead
👉 Better control


3. Virtual Threads (Project Loom — GAME CHANGER)

Code (Java 21+)

package org.code2java;

public class MyVirtualThread {

    public static void main(String[] args) {

        Runnable job1 = new BatchJob("Job-1");
        Runnable job2 = new BatchJob("Job-2");

        Thread vt1 = Thread.startVirtualThread(job1);
        Thread vt2 = Thread.startVirtualThread(job2);

        try {
            vt1.join();
            vt2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

What’s happening internally?

👉 Virtual threads are NOT OS threads
👉 Managed by JVM
👉 Mapped to few carrier threads

Why this is revolutionary

👉 Create millions of threads
👉 No thread pool needed
👉 Blocking calls are cheap

Important Detail (Deep Insight)

When Thread.sleep() happens:

👉 Traditional → OS thread blocked
👉 Virtual → thread parked, carrier reused

This is HUGE.

This is Good, but this is even Better – Virtual Threads with Executor (IMPORTANT PATTERN)

package org.code2java;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class VirtualThreadsExample {
    static void main() {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List<Future<?>> futures = List.of(
                    executor.submit(() -> new BatchJob("Job-1").run()),
                    executor.submit(() -> new BatchJob("Job-2").run()),
                    executor.submit(() -> new BatchJob("Job-3").run())
            );

            // Block and surface any exceptions
            for (Future<?> future : futures) {
                future.get(); // throws ExecutionException if task failed
            }
        } catch (ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Why This Pattern Matters (Deep Insight)

This is NOT just syntax sugar.

Let’s trace what happens:

  1. You submit task
  2. Executor creates new virtual thread per task
  3. Virtual thread runs Runnable
  4. If blocking → thread is parked
  5. Carrier thread reused

What Can Go Wrong ⚠️

1. Shared Mutable State

jobParameters.put(jobName, "Completed");

👉 Not thread-safe
👉 Race conditions possible

Fix:

👉 Use ConcurrentHashMap

2. Blocking inside Virtual Threads (Safe but misunderstood)

👉 Blocking is fine
👉 But CPU-heavy tasks still expensive

3. Forgetting join()

👉 Main thread exits early


When NOT to Use 🚫

Traditional Threads

👉 High concurrency systems

ExecutorService

👉 When you need millions of tasks (Virtual threads better)

Virtual Threads

👉 CPU-intensive parallel work (use ForkJoinPool instead)


Real-World Use Case

Imagine:

👉 10,000 API calls
👉 Each waits on DB

Traditional:

❌ 10,000 threads → crash

ExecutorService:

⚠️ Limited by pool

Virtual Threads:

✅ 10,000 virtual threads → smooth


Interview Questions

  1. What are Virtual Threads in Java?
  2. Difference between platform thread and virtual thread?
  3. How does ExecutorService differ from Virtual Threads?
  4. Why is Thread.sleep cheap in virtual threads?
  5. When NOT to use virtual threads?


Related Posts

  • Abstract Class In JAVA

    Hello Friends, This tutorial is for all the Java followers. One of the best feature that is widely used is the term ‘Abstract’. This term can be used as either class or a simple method. An abstract method is any method that is just declared but not instantiated. In other words one can just create…

  • Threads in Java.

    Hello Friends, This is the tutorial for the java developers. One of the most significance feature of core java is Threading. Threading deals with the processing of Threads in a single java program. Let us learn what actually are Threads. *What are Threads? Threads are independently running processes that are isolated from each other upto…

  • Collections In Java.

    Hello friends, Welcome to another tutorial for java followers. You all may have heard about Collections, it is one of the amazing feature in java. Collections are the object for the group of elements, these elements are nothing but the different data structures like as Array Lists, Linked Lists, Vectors, Hash tables,Hash List, Trees, Hash…

  • Java Date Format.

    Hello Friends, This is one of my tutorials regarding java Date format / object in Java. Many of us find it difficult to store the current date from the java application in database. Lets consider MySql as a database in this case. Now when we create a row in the database table that stores date…

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.