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()
Aspect | start() | run() |
---|---|---|
Thread Creation | Creates and starts a new thread. | Does not create a new thread; runs on the calling thread. |
Concurrency | Enables true concurrent execution. | Executes sequentially in the same thread. |
Method Behavior | Calls run() internally on a new thread. | Executes run() like a regular method call. |
Lifecycle Management | Integrates 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:
- 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.
- The
- Native Code Execution:
- The
start()
method in theThread
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.
- The
- 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.
- Once the new thread is created, the thread scheduler (managed by the JVM and the operating system) eventually invokes the
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:
- No new thread is created; the code runs on the current thread (typically the main thread).
- It does not leverage the thread lifecycle or the JVM’s thread management capabilities.
- Any concurrency benefits are lost since the execution is sequential.
Example:
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:
- JVM’s Role:
- The JVM calls the native
start0()
method when you invokestart()
. - This native method is part of the Java Native Interface (JNI), which allows Java to interact with platform-specific code.
- The JVM calls the native
- 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.
- The
- 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()
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
- Always Use
start()
to Enable Concurrency:- Calling
run()
directly defeats the purpose of multithreading.
- Calling
- Native Code Makes
start()
Powerful:- The
start()
method’s reliance on native code ensures efficient and platform-optimized thread creation.
- The
- Understand the Thread Lifecycle:
- Threads have specific states (“New,” “Runnable,” “Running,” and “Terminated”). The
start()
method transitions a thread from “New” to “Runnable.”
- Threads have specific states (“New,” “Runnable,” “Running,” and “Terminated”). The
- Reusable Threads:
- If you need reusable threads, consider using thread pools with
ExecutorService
instead of creating new threads repeatedly.
- If you need reusable threads, consider using thread pools with
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.