Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › Threads and concurrency

Creating threads and thread states

Why does calling run() directly not create a new thread?

Creating a Thread object and having a thread actually run are two different things. This lab uses t.getState() to follow the states that one thread goes through from the time it is created until it terminates.

Checked against real execution · The 24 combinations were run on JDK 21 and checked against the results

Concepts in this lab

Processes and threads
A process is a running program, and the operating system gives it its own independent memory space. A thread is a flow of execution that does the actual work inside a process. The threads of one process share memory (heap, code, and data), and each has only its own stack. This makes it easy for threads to exchange data, but it also causes problems when they touch the same data at the same time.
Thread states
These are the six states that a Java thread can be in from the time it is created until it terminates. A thread is in exactly one of them at any moment, and you can check which one with t.getState(). NEW is the state in which only the Thread object has been created and the thread has not started yet, RUNNABLE is the state in which it is running or waiting for its turn to run, and TERMINATED is the state in which it has completed execution. In between, a thread becomes BLOCKED when it waits for a lock that another thread holds, WAITING when it waits with no time limit, and TIMED_WAITING when it waits for a specified time. A thread that has become TERMINATED never returns to another state.
start() and run()
These are two methods of Thread: start() creates a new thread and has that thread execute run(), and run() is an ordinary method that holds the work the thread is to do. So if you call run() directly, no new thread is created, and the calling thread runs that code in order. start() can be called only once on a Thread object, and a second call throws an IllegalThreadStateException. To run the same task again, create a new Thread object.
join()
This method makes the current thread wait until another thread terminates. A thread that calls t.join() does not go on to the next line until t terminates, so it is used when the caller needs to receive and use a result that another thread produces. While it waits, the state of the calling thread is WAITING, and when a time is given, as in join(1000), it is TIMED_WAITING. When it is called on a thread that has already terminated or has not started yet, it returns right away without waiting.
Interrupt
This is a signal sent to a waiting thread to tell it to stop waiting. When t.interrupt() is called, the interrupt status of t, the mark that says "this thread was interrupted", is set, and if t was waiting in sleep, join, or wait, that method throws an InterruptedException and the thread wakes up right away. It is not a feature that forces a thread to stop, so what to do after receiving the signal is decided by the code of that thread. A thread in the BLOCKED state, waiting for the lock of synchronized, is not woken up by an interrupt.

Try these

Running with the default settings

  1. new Thread(task, "worker"): This creates the Thread object t. So far only the object exists, and no new flow of execution (thread) has started. That is why t.getState() is NEW.
  2. t.run(): run() is an ordinary method call. No new thread is created, and the main thread runs the code of the task directly. The state of t stays NEW.
  3. Thread.currentThread().getName(): The name of the thread that runs the first line of the task is main. No thread named worker has ever come into being.
  4. Thread.sleep(1000): The main thread itself sleeps for 1 second. During that time main stays inside t.run() and cannot go on to the next line. After 1 second the task ends and t.run() returns.
  5. t.getState() after Thread.sleep(100): start() has never been called on t, so t is still NEW. The task ran, but it was not the thread t that ran it.
  6. t.join(): With a thread that was never started, there is nothing to wait for, so join() returns right away.
  7. t.getState(): The state is NEW to the very end. The task ran, but t was never started.

Result of this run

Code

Runnable task = () -> {
    System.out.println(Thread.currentThread().getName());   // the name of the thread that runs this line
    try {
        Thread.sleep(1000);                 // sleeps for 1 second
    } catch (InterruptedException e) {
        System.out.println("InterruptedException");
    }
};

Thread t = new Thread(task, "worker");
System.out.println(t.getState());

t.run();                                    // calls the method directly
Thread.sleep(100);                          // waits briefly so that the task can make progress
System.out.println(t.getState());
t.join();                                   // waits until t terminates
System.out.println(t.getState());

Assumptions of this model

The results of the 24 combinations (the name of the thread that ran the task, t.getState() partway and at the end, the exceptions thrown, and the interrupt status when the task ends) were checked against the results of running them on JDK 21 on this PC. In the code on the screen, main waits briefly with Thread.sleep(100) and then looks at the state, but these 100 milliseconds are only a time that is "usually enough", not a guaranteed one. So that the check would not depend on timing, the state of t was checked repeatedly until it had left RUNNABLE and stayed where it was. If t.getState() is called right after start(), the state can be RUNNABLE or already another state, so that value was not used as a result. RUNNABLE does not distinguish "running" from "waiting for a turn on the CPU", and Java does not report the two separately. WAITING also results from Object.wait() or LockSupport.park() besides join, but this lab covers only join. The problems of data that several threads share are covered in the "Race condition" lab, and how to create threads in advance and reuse them is covered in the "Thread pool" lab.

Previous lab: Processes and threads · Next lab: Race condition

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these