JAVA β€’ JAVA-SPECIFIC TOPICS

Runnable & Thread β€” Use Runnable/Thread at an introductory level (conceptual)

Learn how Java programs do multiple things at once using threads and the Runnable interface.

Historical Context & Motivation

In the early days of computing, programs ran one instruction at a time from start to finish. If a program needed to download a file and also update the screen, it had to finish the download completely before it could refresh the display. This made programs feel slow and unresponsive, even though the computer itself was fast. The solution was multithreading β€” the ability for a single program to do multiple things at the same time by dividing work into separate paths of execution called threads.

1960s
Time-Sharing Operating Systems
Early mainframes introduced the concept of sharing processor time among multiple users and tasks. This planted the seed for concurrent programming.
1988
POSIX Threads (pthreads)
The POSIX standard formalized how threads should work across Unix systems, making multithreaded C programs portable between different machines.
1995
Java 1.0 Ships with Built-In Threads
Java was one of the first mainstream languages to include threading as a core feature. The Thread class and Runnable interface were part of the language from day one.
2004
Java 5 β€” java.util.concurrent
Java added higher-level concurrency utilities like thread pools and executors, but Runnable and Thread remained the foundational building blocks.
2014
Java 8 β€” Lambdas Simplify Runnable
Lambda expressions made it possible to write a Runnable in a single line of code, making thread creation easier than ever.

Today, almost every application β€” from web servers to mobile apps to video games β€” uses threads to stay responsive. Java's Runnable interface and Thread class give you the tools to split a program into multiple concurrent paths of execution. The key question this lesson addresses: how do you tell Java what to do (Runnable) and where to do it (Thread)?

Core Principles & Definitions

Before writing any code, you need to understand five foundational ideas that govern how Java handles concurrency. These concepts form the mental model you will use every time you work with threads.

1

Thread

A thread is an independent path of execution within a program. Think of it as a separate worker that can run code at the same time as other workers. In Java, the Thread class represents and controls one such worker.
2

Runnable Interface

The Runnable interface defines a single method, run(). Any class that implements Runnable is essentially saying, 'I have a job to do.' The job's instructions go inside the run() method.
3

Main Thread

Every Java program starts with a main thread β€” the thread that runs your main() method. Without creating additional threads, everything happens sequentially on this single thread.
4

start() vs. run()

Calling start() on a Thread object launches a new thread of execution. Calling run() directly just runs the code on the current thread β€” no new thread is created. This is the most common beginner mistake.
5

Concurrency vs. Parallelism

Concurrency means managing multiple tasks that can overlap in time. Parallelism means tasks physically execute at the same instant on separate CPU cores. Threads enable both, depending on the hardware.
✦ KEY TAKEAWAY
Think of a restaurant kitchen. A Runnable is like a recipe card β€” it describes what needs to be done. A Thread is like a chef β€” the worker who reads the recipe and actually does the cooking. You can hand the same recipe to multiple chefs, and they can all cook at the same time. Separating the 'what' (recipe) from the 'who' (chef) is the key design idea behind Runnable and Thread.

Visual Explanation β€” Thread Lifecycle

A thread in Java goes through several states during its life. Understanding this lifecycle helps you predict what your code will do when threads interact. The diagram below shows the five major states a thread can be in, and the transitions between them.

A thread begins in the NEW state when you create it. Calling start() moves it to RUNNABLE. The operating system schedules it to RUNNING. It may become BLOCKED if it waits for a resource. Once run() completes, the thread is TERMINATED and cannot be restarted.

Notice that once a thread reaches the TERMINATED state, you cannot call start() on it again. If you need the same task to repeat, you must create a new Thread object. This is another reason why separating the task (Runnable) from the worker (Thread) is so useful β€” you can reuse the same Runnable with a fresh Thread.

How It Works β€” Two Ways to Create Threads

Java gives you two approaches to create a new thread. Both accomplish the same goal, but they differ in flexibility and design. Understanding both methods β€” and when to prefer one over the other β€” is essential.

Approach 1 β€” Extending the Thread Class

You can create a subclass that extends Thread and override its run() method. When you call start() on an instance of your subclass, Java creates a new thread and executes your run() method on it. Here is the pattern:

πŸ’» CODE PATTERN β€” Extending Thread
class MyTask extends Thread { public void run() { System.out.println("Running in: " + getName()); } } MyTask t = new MyTask(); t.start(); // launches a NEW thread Because MyTask already extends Thread, it cannot extend any other class. Java only allows single inheritance.

Approach 2 β€” Implementing the Runnable Interface

The preferred approach is to implement the Runnable interface. You write a class that implements Runnable, define your task in run(), and then pass an instance to a Thread constructor. This separates the task from the thread machinery.

πŸ’» CODE PATTERN β€” Implementing Runnable
class MyTask implements Runnable { public void run() { System.out.println("Running in: " + Thread.currentThread().getName()); } } Thread t = new Thread(new MyTask()); t.start(); // launches a NEW thread Because MyTask only implements an interface, it is free to extend another class if needed. This makes Runnable much more flexible.

The Critical Difference: start() vs. run()

If you accidentally call t.run() instead of t.start(), Java will execute the run() method on the current thread β€” no new thread is created. Your code will still work, but it will run sequentially. Always use start() to actually launch a new thread.

Extends Thread vs. Implements Runnable

Now that you have seen both approaches, let's compare them side by side. The diagram below illustrates how the class relationships differ, and the table that follows summarizes the practical trade-offs.

On the left, extending Thread uses up your one allowed superclass. On the right, implementing Runnable only uses an interface, so your class remains free to extend another class. The Runnable approach also separates the task logic from the threading mechanism.
Comparison of the two threading approaches in Java
FeatureExtends ThreadImplements Runnable
InheritanceCannot extend another classFree to extend any class
Separation of concernsTask and thread are mixed togetherTask is cleanly separated from thread
ReusabilityHarder to reuse the task elsewhereSame Runnable can be passed to multiple Threads or a thread pool
Code simplicitySlightly less code (no separate Thread constructor)One extra line to create the Thread object
Industry preferenceRarely used in production codePreferred in professional Java

Worked Example β€” Countdown with Two Threads

Let's trace through a complete example where two threads count down simultaneously. We will use the Runnable approach since it is the recommended practice.

Two Threads Counting Down at the Same Time
1
Step 1 β€” Define the Task with RunnableCreate a class that implements Runnable. Inside run(), write a loop that counts from 3 down to 1 and prints the thread's name along with the number. class Countdown implements Runnable { public void run() { for (int i = 3; i >= 1; i--) { System.out.println( Thread.currentThread().getName() + ": " + i); } } }
The Countdown class now holds the task logic, completely independent of any specific thread.
2
Step 2 β€” Create Thread ObjectsIn your main() method, create a single Runnable instance and pass it to two separate Thread constructors. Give each thread a descriptive name. Runnable task = new Countdown(); Thread t1 = new Thread(task, "Thread-A"); Thread t2 = new Thread(task, "Thread-B");
Both threads reference the same Runnable object. At this point, both threads are in the NEW state.
3
Step 3 β€” Start the ThreadsCall start() (not run()!) on each thread to move them into the RUNNABLE state. t1.start(); t2.start();
The JVM now has three threads running: the main thread, Thread-A, and Thread-B.
4
Step 4 β€” Observe Interleaved OutputBecause both threads run concurrently, their output may interleave in unpredictable order. One possible output is shown below. Running the program again might produce a different order. Thread-A: 3 Thread-B: 3 Thread-A: 2 Thread-B: 2 Thread-B: 1 Thread-A: 1
The interleaving proves that both threads are executing concurrently. The exact order depends on how the operating system schedules each thread.
⚠️ Common Mistake
If you had written t1.run(); t2.run(); instead of t1.start(); t2.start();, the output would always be Thread-A: 3, 2, 1 followed by Thread-B: 3, 2, 1 β€” completely sequential, with no concurrency at all.

Strengths & Limitations of Basic Threading

Using Runnable and Thread directly gives you full control, but it also comes with responsibilities. The table below summarizes the key strengths and limitations you should be aware of as you begin working with threads.

Strengths and limitations of using Runnable and Thread
StrengthsLimitations
Built into Java β€” no external libraries neededCreating too many threads can slow your program down instead of speeding it up
Fine-grained control over each thread's behaviorYou must manually manage thread creation, starting, and cleanup
Makes programs responsive β€” one thread can handle user input while another does heavy computationShared data between threads can cause race conditions (bugs where two threads modify data at the same time)
Runnable separates the task from threading, promoting clean designThe run() method cannot return a value or throw checked exceptions
Foundation for all higher-level concurrency tools in JavaDebugging multithreaded code is significantly harder than single-threaded code
✦ KEY TAKEAWAY
Think of threads like lanes on a highway. Adding a second lane (thread) can double throughput, but adding 500 lanes to a two-mile stretch creates chaos β€” merging, collisions, and overhead. Similarly, creating thousands of threads wastes memory and CPU time on scheduling. In real-world Java, thread pools (covered in later lessons) help manage this, but Runnable and Thread are the building blocks underneath.

Connection to Advanced Concurrency

Once you are comfortable with Runnable and Thread, Java provides a whole ecosystem of more powerful tools built on top of these basics. The table below previews how these foundational concepts connect to what you will encounter next.

How Runnable and Thread connect to advanced Java concurrency
Basic ConceptAdvanced EquivalentWhat It Adds
RunnableCallable<V>Can return a value and throw checked exceptions
new Thread(r).start()ExecutorServiceManages a pool of reusable threads automatically
Manual synchronizationjava.util.concurrent locksMore flexible and efficient locking strategies
Checking thread completion manuallyFuture<V>Represents a result that will be available later
Shared mutable dataAtomicInteger, ConcurrentHashMapThread-safe data structures that avoid race conditions

Every one of these advanced tools depends on the same core ideas you have learned in this lesson: a task defines what to execute, and a thread (or thread pool) determines where and when to execute it. Mastering Runnable and Thread gives you a solid foundation for everything that follows. As you move into AP Computer Science or college-level courses, you will see these patterns everywhere β€” from Android app development to building web servers.

Practice Problems

PROBLEM 1 β€” CONCEPTUAL
Explain in your own words the difference between calling myThread.start() and calling myThread.run(). Why does this distinction matter?
PROBLEM 2 β€” BASIC CALCULATION
A Java program creates 4 Thread objects, each given the same Runnable that prints one line. The main method calls start() on all four threads. How many threads are active in the program at most (including the main thread)? How many total lines will be printed?
PROBLEM 3 β€” INTERMEDIATE
You have a class Animal that you want to extend, and you also need to run some code in a new thread. Why can't you simply extend both Animal and Thread? Describe how using Runnable solves this problem, and write the class signature (just the first line) that makes this work.
PROBLEM 4 β€” APPLIED
You are building a simple chat application. The program needs to simultaneously listen for incoming messages from the network and read user input from the keyboard. Describe how you would use Runnable and Thread to structure this. How many threads would you create, and what would each one do?
PROBLEM 5 β€” CRITICAL THINKING
Two threads both execute the following Runnable, which increments a shared variable count from 0 to 1000 in a loop. After both threads finish, would you always expect count to equal 2000? Why or why not? (Hint: think about what happens when two threads read and write the same variable at the exact same moment.)

Lesson Summary

Java provides two fundamental approaches to multithreading. A Runnable is an interface with a single run() method that defines the task β€” the work to be done. A Thread is the worker that executes that task on a separate path of execution. You can create threads by extending Thread or by implementing Runnable and passing it to a Thread constructor. The Runnable approach is preferred because it preserves inheritance flexibility and cleanly separates the task from the threading mechanism.

Always call start() to launch a new thread β€” calling run() directly runs code on the current thread with no concurrency. A thread moves through the states NEW β†’ RUNNABLE β†’ RUNNING β†’ TERMINATED, and once terminated, it cannot be restarted. While basic threading gives you fine-grained control, be aware of challenges like race conditions when threads share data. These foundational concepts β€” Runnable and Thread β€” are the building blocks for all of Java's advanced concurrency tools, including Callable, ExecutorService, and Future.

Varsity Tutors β€’ Java β€’ Runnable & Thread β€” Use Runnable/Thread at an introductory level (conceptual)