Concurrency and Multithreading Java

Understanding Thread.start() and Runnable.run() in Java: A Comprehensive Guide

In Java, threads are a fundamental way to achieve concurrent programming. However, one of the common points of confusion is the difference between calling Thread.start() and calling Runnable.run() directly. This blog will clarify these differences and dive into the internal workings of the start() method, especially how it leverages native (platform-specific) code.


Key Differences Between Thread.start() and Runnable.run()

Aspectstart()run()
Thread CreationCreates and starts a new thread.Does not create a new thread; runs on the calling thread.
ConcurrencyEnables true concurrent execution.Executes sequentially in the same thread.
Method BehaviorCalls run() internally on a new thread.Executes run() like a regular method call.
Lifecycle ManagementIntegrates with the thread lifecycle and scheduler.Ignores thread lifecycle, acting like a normal method.

What Happens When You Call start()?

When you call start() on a thread, the following steps occur:

  1. Thread Transition to “Runnable” State:
    • The start() method transitions the thread from the “New” state to the “Runnable” state.
    • This does not mean the thread starts running immediately; it becomes eligible to run when the CPU scheduler picks it up.
  2. Native Code Execution:
    • The start() method in the Thread class is implemented as a native method (written in platform-specific code, such as C or C++).
    • This native implementation interacts with the operating system to create and manage a new thread.
  3. Scheduler Invokes run():
    • Once the new thread is created, the thread scheduler (managed by the JVM and the operating system) eventually invokes the run() method.
    • The run() method contains the logic you defined for the thread to execute.

What Happens When You Call run() Directly?

The run() method is just a regular Java method in the Thread or Runnable class. When you call it directly:

  1. No new thread is created; the code runs on the current thread (typically the main thread).
  2. It does not leverage the thread lifecycle or the JVM’s thread management capabilities.
  3. Any concurrency benefits are lost since the execution is sequential.

Example:

Java
class MyThread extends Thread {
    public void run() {
        System.out.println("Running in thread: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.run(); // Executes on the main thread, no new thread is created
        System.out.println("Main thread: " + Thread.currentThread().getName());
    }
}

Output:

Running in thread: main
Main thread: main

The start() Method and Native Code

The start() method is implemented in the Thread class, and its native counterpart interacts with the operating system to create a new thread. Here’s what happens under the hood:

  1. JVM’s Role:
    • The JVM calls the native start0() method when you invoke start().
    • This native method is part of the Java Native Interface (JNI), which allows Java to interact with platform-specific code.
  2. Native Thread Creation:
    • The start0() method interacts with the operating system to allocate resources and initialize a new thread.
    • It integrates with the OS thread scheduler to determine when the thread gets CPU time.
  3. Thread Scheduling:
    • The actual scheduling of threads is handled by the operating system.
    • Java provides the abstraction, but the platform’s native libraries control the thread lifecycle.

Key Point: The use of native code ensures that Java threads integrate seamlessly with the underlying platform’s threading model, offering high performance and compatibility.


Example: Demonstrating start() vs run()

Java
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Running in thread: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable task = new MyRunnable();
        
        // Case 1: Using Thread.start()
        Thread thread = new Thread(task);
        thread.start(); // Creates a new thread

        // Case 2: Calling run() directly
        task.run(); // Executes in the current thread (main thread)

        System.out.println("Main thread: " + Thread.currentThread().getName());
    }
}

Output Example:

Running in thread: Thread-0
Running in thread: main
Main thread: main

Practical Takeaways

  1. Always Use start() to Enable Concurrency:
    • Calling run() directly defeats the purpose of multithreading.
  2. Native Code Makes start() Powerful:
    • The start() method’s reliance on native code ensures efficient and platform-optimized thread creation.
  3. Understand the Thread Lifecycle:
    • Threads have specific states (“New,” “Runnable,” “Running,” and “Terminated”). The start() method transitions a thread from “New” to “Runnable.”
  4. Reusable Threads:
    • If you need reusable threads, consider using thread pools with ExecutorService instead of creating new threads repeatedly.

Conclusion

Understanding the distinction between Thread.start() and Runnable.run() is essential for effective multithreaded programming in Java. While run() is just a normal method, start() leverages the power of native code to create a new thread and enable concurrency. By appreciating the underlying mechanics, you can write more efficient and robust multithreaded applications.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Java Java 8 Features

Avoid NullPointerException in Java with Optional: A Comprehensive Guide

Are you tired of dealing with avoid NullPointerExceptions(NPEs) in your Java code? Look no further than the Optional class introduced
Blog Java Java 8 Features

Unlocking the Power of Java 8 Streams: A Guide to Efficient Data Processing

What are Java 8 Streams? At its core, a stream in Java 8 is a sequence of elements that facilitates