Creating threads in Java
Thread + Runnable, Thread.ofPlatform, start() vs run().
Task vs worker
A Runnable is the task: what to do. A Thread is the worker: who does it. You hand the task to the worker, then tell the worker to go.
Runnable task = () -> System.out.println("hi");
Thread t = new Thread(task);
t.start(); // a new thread runs taskWhat start() really does
start() asks the JVM to create a new thread, and that new thread then calls run(). start() returns immediately, so the caller keeps going in parallel. Calling run() yourself is just an ordinary method call on the current thread: no new thread at all.
Your turn
What does this print?
Thread t = new Thread(() ->
System.out.println(
Thread.currentThread().getName()));
t.setName("worker");
t.start();
t.join();workermainThread-0
Show the answer
worker. start() launches the new thread, and the lambda runs on it, so currentThread() is the thread we named "worker". join() makes main wait until it has printed.
start() vs run()
Thread t = new Thread(() -> download());
t.run();
showUi();run() executes the download on the calling thread. The caller blocks until it's done, and currentThread() inside is still main.
Thread t = new Thread(() -> download());
t.start();
showUi();start() creates a new thread for the download; the caller moves on to showUi() immediately.
Threads are single-use
A Thread object can be started only once. Even after it has finished, a second start() throws IllegalThreadStateException. Need to run the task again? Create a new Thread, or use a thread pool.
Thread t = new Thread(() -> {});
t.start();
t.join();
t.start(); // IllegalThreadStateExceptionThe modern builder
Since Java 21, Thread.ofPlatform() returns a builder. Name the thread, then start(task) creates and starts it in one call. If you want to start it later, use unstarted(task) instead.
Thread w = Thread.ofPlatform()
.name("io-1")
.start(() -> fetch());
Thread later = Thread.ofPlatform()
.unstarted(() -> fetch());
later.start();On the job
UI apps have one thread that draws the screen. Run a slow download on it and the app freezes. The run()-instead-of-start() slip does exactly that: the "background" job silently runs on the caller. Android is so strict about this that network calls on its main thread throw NetworkOnMainThreadException.
Key takeaways
- Runnable = the task; Thread = the worker
- start() launches a new thread; run() is a plain method call
- A Thread object can be started only once
- Thread.ofPlatform().name("w").start(task) builds and starts
Thread itself implements Runnable. That's why a run() method exists on every Thread at all, and why calling it by mistake compiles without a peep.
Practice questions
What does this print?
Thread t = new Thread(() ->
System.out.println(
Thread.currentThread().getName()));
t.setName("worker");
t.run();- main
- worker
- Thread-0
- Throws IllegalThreadStateException
Check your answer
main. run() is an ordinary method call, so the lambda executes on the calling thread — main. Only start() would run it on the new thread named "worker".
What does this print?
Thread t = new Thread(() -> {});
t.start();
t.join();
t.start();- Throws IllegalThreadStateException
- Runs the task a second time
- Nothing; the second start() is ignored
- Compile error
Check your answer
Throws IllegalThreadStateException. A Thread object is single-use. Once it has been started, calling start() again throws IllegalThreadStateException — create a new Thread (or use a pool) instead.