Concepts / Runnable Interface

Runnable Interface

A functional interface is a Java interface with a single abstract method, and Runnable is a standard example with one method, run.

  • Programming

Predicting What a Small Program Prints

Before any theory, read a very small Java program and predict what it prints. The program below uses a lambda expression in a place where many Java programmers would otherwise write a named class. Do not worry yet about the arrow symbol. Focus on the variable called greeting and the method call on its last line.

java

What do you think happens?

What does this program print when greeting.run() is called?

  • Nothing, because a lambda cannot be run

  • Hello from a lambda

  • The text greeting.run()

Reveal answer
Answer:

Hello from a lambda

The variable greeting has the type Runnable, and the lambda on the right supplies the body of Runnable's single method, run(). Calling greeting.run() executes that body, which prints the message.

Functional Interfaces and Their Single Abstract Method

Definition

A functional interface is a Java interface with exactly one abstract method. That single method defines the shape of a piece of behavior: what inputs it takes, what it returns, and what it is called.

The interface itself does not say how the behavior is carried out. It only provides a template that some implementation must fill in. Think of the interface as a blank form with one question on it. Whoever fills in the answer decides what the behavior actually does.

Key point

To identify a functional interface, count its abstract methods. If there is exactly one, the interface is functional, and a lambda expression can supply a value of that interface type.

Runnable as the Simplest Example

The Runnable interface shows the idea in its simplest form. Its declaration contains exactly one method, named run, which takes no parameters and returns nothing. Because the interface has a single method to implement, a lambda expression can stand in for a complete implementation of it.

Pattern
public void run()

Read this signature as three parts that matter. The method is public, so code outside the class can call it. It returns void, so it produces no value. It takes no parameters, so everything run() needs must be available from the object's own fields or from the lambda's surrounding code. The name run is fixed by the interface; you cannot choose a different name and still satisfy Runnable.

Lambda Expressions as Implementations

Go back to the first program. The empty parentheses on the left stand for the parameter list of run, which has no parameters. The body on the right is the code that runs when run is called. The lambda therefore carries exactly the information the interface template leaves open: the implementation of the one method.

The same pattern works for any interface you define yourself, as long as it has one abstract method. The example below is original and is not from the source. It declares an interface with one method that takes a String and returns a String. Notice that the lambda's parameter list matches the parameter list of that method.

java
Output
HI!
Notes on the code
  • Transformer has exactly one abstract method, apply, so it is functional.
  • The lambda s -> ... has one parameter, matching apply(String s).
  • The body computes the return value that apply must produce.

Interface Names as Types

The name of a functional interface is a type, in the same way that String and double are types. This matters because you can use the name to declare variables, to declare parameters, and to specify what a method returns. Once a type is a functional interface, any value of that type can be created by writing a lambda expression instead of a conventional class.

java
Output
task ran
task ran
Notes on the code
  • The return type Runnable tells callers that makeTask produces a Runnable value.
  • The lambda returned is a value of type Runnable, so the caller can invoke run on it.
  • The second call creates and runs a fresh lambda in one expression.

The type name does the work of connecting the lambda to the method it implements. The caller of makeTask never needs to know that a lambda was used; it only needs to know the value has type Runnable.

Task Versus Thread

Earlier in the chapter, a thread was programmed by defining a subclass of Thread. The second way is to define a class that implements the interface Runnable. Both approaches let you write the code a thread should execute. The difference is in what your class is. With the subclass approach, your class is itself a thread. With the Runnable approach, your class is only a task, and a separate Thread object is the thing that carries out that task.

Key point

A Runnable describes work; a Thread object carries that work on its own thread of control. Keeping the task and the thread separate is the central idea of the Runnable approach.

Tracing a Runnable Passed to a Thread

The following class is an original example in the style the source describes. It declares implements Runnable, stores a name in a field, and has a run() method that prints a message using that field. It does not extend Thread and does not call start(). It only describes a task.

java
java
Output
Hello, Ada
Notes on the code
  • new Greeter("Ada") builds the task object; its name field is set to Ada.
  • new Thread(...) builds a Thread object and hands it the Runnable.
  • start() creates the new thread, which then calls the task's run() method.

Follow the path step by step. The Greeter object is created first and holds the name. The Thread constructor accepts that Runnable, so the thread's run() forwards to the Greeter's run(). Nothing new runs yet. The call to start() is the step that creates the separate thread of control, and on that thread the Greeter's run() executes and prints the message.

main threadcalls start()Thread objectholds the Greeter tasknew threadcreated by start()Greeter run()prints Hello, Adanew Thread(task)start()createsrun()
When start() is called, which thread executes run(), and what steps happen between the call and run() starting? The main thread creates the objects and calls start(); the new thread then runs the task's run() method.

How the Thread, Task, and run() Method Relate

The Thread class has a constructor that takes a Runnable as its parameter. When you pass an object that implements Runnable to that constructor, the thread's run() method simply calls the run() method of the Runnable you supplied. The thread does not contain the task; it forwards to the task.

Thread objectcarries the workGreeter objectimplements Runnablerun() methodprints the messageholds Runnabledefines
How are the Thread object, the Runnable object, and the run() method related, and which one holds a reference to which? The Thread holds the task, and the task's run() method holds the work.

start() Versus Calling run() Directly

A common confusion is whether calling run() directly is the same as calling start(). The source makes the difference clear: start() is the call that creates the new thread of control in which run() executes. Calling run() as an ordinary method call does not create a new thread. The method body runs on whichever thread made the call.

new threadcreated by start()no new threadnothing createdrun() on new threadseparate from mainrun() on main threadordinary method call
How does calling run() directly differ from calling start(), and on which thread does each version execute? Both produce the same message, but only start() moves the work onto a new thread.

Mistakes When Tracing Runnable Programs

When a program using Runnable produces an unexpected result, the cause is usually one of a small number of mistakes. Each one leaves a trace that you can follow back to its source.

  • Common mistake

    Expecting output from a Thread that was never started

    What it looks like
    Thread t = new Thread(new Greeter("Ada")); with no t.start() call
    Why it fails

    Creating the Thread object alone does not start any new work.

    Do this instead

    Call start() on the Thread so that its run() method executes.

  • Common mistake

    Assuming run() creates a new thread

    What it looks like

    Calling greeter.run() directly and expecting a separate thread

    Why it fails

    Only start() creates the new thread; a direct run() call executes on the calling thread.

    Do this instead

    Use start() when the work should run on its own thread.

  • Common mistake

    Believing the Thread itself contains the task

    Why it fails

    The thread forwards its run() to the Runnable you supplied.

    Do this instead

    Look at the Runnable object's run() method to find the printed text.

  • Common mistake

    Trying to use a different method name

    Why it fails

    The name run is fixed by the interface, so a class with a differently named method does not satisfy Runnable.

Practice Tracing a Runnable

MEDIUM

Without running the code, trace the following program. Name which thread prints each line and say whether the lines may appear in a different order than written. Then write one line of Java that would make the task run on a new thread.

Hints
  • Ask whether start() is ever called.
  • Ask which thread runs the task's run() method when it is called directly.
java

Key Takeaways

  • A functional interface has exactly one abstract method, and Runnable is a standard example whose single method is public void run().
  • A lambda expression can supply the body of a functional interface's method, and the interface name works as a type for variables, parameters, and return values.
  • A Runnable describes work; a Thread object carries that work, and its run() forwards to the Runnable's run().
  • start() creates the new thread in which run() executes, while calling run() directly runs on the calling thread.
  • When tracing output, check whether start() was called, which thread made each call, and whether the method invoked is the one you expect.