start() schedules a new thread on which the JVM later invokes run(); calling run() directly is just an ordinary method call on the current thread — no new thread, no concurrency. This is a classic trick question.
What each one does
start() is a native-backed method that registers the thread with the scheduler, moves it from NEW to RUNNABLE, and the OS eventually calls run() on that fresh stack. run() is just the method holding your code; invoking it yourself executes it inline.
Runnable task = () -> System.out.println(Thread.currentThread().getName());
Thread t = new Thread(task, "worker");
t.run(); // prints "main" -> ran on the caller's thread
t.start(); // prints "worker" -> ran on the new thread
start() can be called only once
start() flips an internal state guard. Calling it a second time throws IllegalThreadStateException — a thread is one-shot. run(), being a normal method, can be called any number of times (always on the current thread).
Thread t = new Thread(() -> {});
t.start();
t.start(); // IllegalThreadStateException
Why it matters
The whole point of a thread is parallel execution. If you call run(), you've written sequential code that merely looks concurrent — a bug that silently destroys throughput and any expected interleaving. Calling run() on a Thread you intended to start also means nothing actually runs in the background, so join() on it returns immediately.