Thread Creation in Java
Learn how to create and start threads in Java programs
Think of threads like workers in a restaurant! Just like a restaurant can have multiple chefs cooking different dishes at the same time, your Java program can have multiple threads doing different tasks at the same time. Instead of waiting for one task to finish before starting another, threads let your program do many things at once!
The Problem Without Threads
Imagine you're running a pizza shop. If you only have one worker, they have to take an order, make the pizza, bake it, and serve it before helping the next customer. Customers have to wait a long time! Let's see this problem in code:
// Without threads - everything happens one after anotherpublic class SingleThreadExample { public static void main(String[] args) { System.out.println("Starting pizza shop..."); // Task 1: Take order (takes 2 seconds) takeOrder("Margherita Pizza"); // Task 2: Make pizza (takes 5 seconds) makePizza("Margherita Pizza"); // Task 3: Bake pizza (takes 10 seconds) bakePizza("Margherita Pizza"); System.out.println("Pizza shop closed for the day!"); } static void takeOrder(String pizzaType) { System.out.println("Taking order for: " + pizzaType); try { Thread.sleep(2000); // Simulate 2 seconds } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Order taken!"); } static void makePizza(String pizzaType) { System.out.println("Making: " + pizzaType); try { Thread.sleep(5000); // Simulate 5 seconds } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Pizza made!"); } static void bakePizza(String pizzaType) { System.out.println("Baking: " + pizzaType); try { Thread.sleep(10000); // Simulate 10 seconds } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Pizza ready!"); }}/* Output:Starting pizza shop...Taking order for: Margherita PizzaOrder taken!Making: Margherita PizzaPizza made!Baking: Margherita PizzaPizza ready!Pizza shop closed for the day!Total time: 17 seconds (2 + 5 + 10)*/Creating Threads by Extending Thread Class
The first way to create a thread is by extending the Thread class. It's like hiring a specialized worker who knows exactly what to do! You create a class that extends Thread and override the run() method with the task you want the thread to perform.
// Creating a thread by extending Thread classclass PizzaMaker extends Thread { private String pizzaType; public PizzaMaker(String pizzaType) { this.pizzaType = pizzaType; } // The run() method contains the code that will run in this thread @Override public void run() { System.out.println(pizzaType + " - Starting to make pizza..."); try { Thread.sleep(5000); // Takes 5 seconds to make } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(pizzaType + " - Pizza is ready!"); }}public class ThreadExtendExample { public static void main(String[] args) { System.out.println("Pizza shop opening..."); // Create three pizza maker threads PizzaMaker maker1 = new PizzaMaker("Margherita"); PizzaMaker maker2 = new PizzaMaker("Pepperoni"); PizzaMaker maker3 = new PizzaMaker("Hawaiian"); // Start all threads - they all work at the same time! maker1.start(); maker2.start(); maker3.start(); System.out.println("All pizza makers are working!"); }}/* Output (approximately):Pizza shop opening...All pizza makers are working!Margherita - Starting to make pizza...Pepperoni - Starting to make pizza...Hawaiian - Starting to make pizza...Margherita - Pizza is ready!Pepperoni - Pizza is ready!Hawaiian - Pizza is ready!Total time: ~5 seconds (all work in parallel!)*/Creating Threads by Implementing Runnable
The second way is to implement the Runnable interface. This is like creating a job description that any worker can follow! This approach is more flexible because Java doesn't support multiple inheritance, but a class can implement multiple interfaces.
// Creating a thread by implementing Runnableclass OrderTaker implements Runnable { private String customerName; public OrderTaker(String customerName) { this.customerName = customerName; } @Override public void run() { System.out.println("Taking order from: " + customerName); try { Thread.sleep(2000); // Takes 2 seconds } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Order from " + customerName + " is complete!"); }}public class RunnableExample { public static void main(String[] args) { System.out.println("Restaurant opening..."); // Create Runnable tasks OrderTaker task1 = new OrderTaker("Alice"); OrderTaker task2 = new OrderTaker("Bob"); OrderTaker task3 = new OrderTaker("Charlie"); // Create Thread objects and pass the Runnable tasks Thread waiter1 = new Thread(task1); Thread waiter2 = new Thread(task2); Thread waiter3 = new Thread(task3); // Start all threads waiter1.start(); waiter2.start(); waiter3.start(); System.out.println("All waiters are serving customers!"); }}/* Output:Restaurant opening...All waiters are serving customers!Taking order from: AliceTaking order from: BobTaking order from: CharlieOrder from Alice is complete!Order from Bob is complete!Order from Charlie is complete!*/Using Lambda Expressions (Modern Way)
With Java 8+, we can use lambda expressions to create threads more easily! Since Runnable is a functional interface (has only one method), we can write it as a short, clean lambda expression. It's like giving quick instructions to a worker!
// Creating threads with lambda expressionspublic class LambdaThreadExample { public static void main(String[] args) { System.out.println("Starting tasks..."); // Method 1: Lambda with Runnable Thread downloadThread = new Thread(() -> { System.out.println("Downloading file..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Download complete!"); }); // Method 2: Even shorter lambda Thread uploadThread = new Thread(() -> { System.out.println("Uploading data..."); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Upload complete!"); }); // Method 3: Inline lambda with start() new Thread(() -> { System.out.println("Processing data..."); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Processing complete!"); }).start(); downloadThread.start(); uploadThread.start(); System.out.println("All tasks running in parallel!"); }}/* Output:Starting tasks...All tasks running in parallel!Downloading file...Uploading data...Processing data...Upload complete!Download complete!Processing complete!*/Thread Lifecycle and Important Methods
Threads have a lifecycle - they're born, they work, and they finish. Understanding this lifecycle helps you control your threads better. Let's explore important thread methods like start(), sleep(), join(), and getName()!
// Understanding thread lifecycle and methodspublic class ThreadLifecycleExample { public static void main(String[] args) { System.out.println("Main thread: " + Thread.currentThread().getName()); // Create a worker thread Thread worker = new Thread(() -> { System.out.println("Worker started: " + Thread.currentThread().getName()); for (int i = 1; i <= 5; i++) { System.out.println("Working on task " + i + "/5"); try { Thread.sleep(1000); // Sleep for 1 second } catch (InterruptedException e) { System.out.println("Worker was interrupted!"); return; } } System.out.println("Worker finished all tasks!"); }, "MyWorkerThread"); // Check thread state before starting System.out.println("State before start: " + worker.getState()); // NEW // Start the thread worker.start(); System.out.println("State after start: " + worker.getState()); // RUNNABLE // Main thread continues running System.out.println("Main thread continues..."); try { // Wait for worker to finish System.out.println("Main thread waiting for worker..."); worker.join(); // Main thread waits here System.out.println("State after join: " + worker.getState()); // TERMINATED } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main thread finished!"); }}/* Output:Main thread: mainState before start: NEWState after start: RUNNABLEMain thread continues...Main thread waiting for worker...Worker started: MyWorkerThreadWorking on task 1/5Working on task 2/5Working on task 3/5Working on task 4/5Working on task 5/5Worker finished all tasks!State after join: TERMINATEDMain thread finished!*/Real-World Example: Download Manager
Let's build a practical download manager that downloads multiple files at the same time! This shows how threads make programs faster and more efficient. Each file downloads in its own thread, so they all download simultaneously instead of one after another.
// Real-world example: Multi-threaded download managerclass FileDownloader implements Runnable { private String fileName; private int fileSizeMB; public FileDownloader(String fileName, int fileSizeMB) { this.fileName = fileName; this.fileSizeMB = fileSizeMB; } @Override public void run() { System.out.println("Starting download: " + fileName + " (" + fileSizeMB + "MB)"); // Simulate download progress for (int i = 1; i <= fileSizeMB; i++) { try { Thread.sleep(500); // Each MB takes 0.5 seconds int percentage = (i * 100) / fileSizeMB; System.out.println(fileName + ": " + percentage + "% complete"); } catch (InterruptedException e) { System.out.println(fileName + ": Download interrupted!"); return; } } System.out.println(fileName + ": Download COMPLETE! ✓"); }}public class DownloadManager { public static void main(String[] args) { System.out.println("=== Download Manager Started ==="); long startTime = System.currentTimeMillis(); // Create download tasks FileDownloader video = new FileDownloader("movie.mp4", 5); FileDownloader music = new FileDownloader("song.mp3", 3); FileDownloader document = new FileDownloader("report.pdf", 2); // Create threads Thread videoThread = new Thread(video); Thread musicThread = new Thread(music); Thread docThread = new Thread(document); // Start all downloads videoThread.start(); musicThread.start(); docThread.start(); try { // Wait for all downloads to complete videoThread.join(); musicThread.join(); docThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); double totalSeconds = (endTime - startTime) / 1000.0; System.out.println("=== All Downloads Complete! ==="); System.out.println("Total time: " + totalSeconds + " seconds"); System.out.println("(Without threads, it would take: " + ((5+3+2) * 0.5) + " seconds)"); }}/* Output:=== Download Manager Started ===Starting download: movie.mp4 (5MB)Starting download: song.mp3 (3MB)Starting download: report.pdf (2MB)movie.mp4: 20% completesong.mp3: 33% completereport.pdf: 50% completemovie.mp4: 40% completesong.mp3: 66% completereport.pdf: 100% completereport.pdf: Download COMPLETE! ✓movie.mp4: 60% completesong.mp3: 100% completesong.mp3: Download COMPLETE! ✓movie.mp4: 80% completemovie.mp4: 100% completemovie.mp4: Download COMPLETE! ✓=== All Downloads Complete! ===Total time: 2.5 seconds(Without threads, it would take: 5.0 seconds)*/Key Concepts
Thread vs Runnable
Extend Thread when you need thread-specific functionality. Implement Runnable when you want to separate the task from the thread mechanism, or when your class already extends another class.
start() vs run()
Always use start() to begin a thread, not run()! Calling start() creates a new thread and executes run() in it. Calling run() directly executes the method in the current thread, defeating the purpose of multithreading.
Thread.sleep()
Makes the current thread pause for a specified time. Useful for simulating delays, but remember it throws InterruptedException which must be handled.
join() Method
Waits for a thread to finish before continuing. Like waiting for a worker to complete their task before moving on. Essential for coordinating thread completion.
Best Practices
- ✓Prefer Runnable over extending Thread - it's more flexible and follows better OOP principles
- ✓Always give your threads meaningful names using the Thread constructor or setName() for easier debugging
- ✓Handle InterruptedException properly - don't just catch and ignore it
- ✓Use join() when you need to wait for threads to complete before proceeding
- ✓Don't call run() directly - always use start() to actually create a new thread
- ✓Keep the run() method focused - break complex tasks into smaller helper methods
Common Mistakes to Avoid
- ✗Calling run() instead of start() - this doesn't create a new thread!
- ✗Calling start() multiple times on the same thread - it will throw IllegalThreadStateException
- ✗Forgetting to handle InterruptedException when using Thread.sleep()
- ✗Creating too many threads - each thread consumes system resources
- ✗Not using join() when you need to wait for thread completion
- ✗Ignoring thread names - unnamed threads make debugging much harder
Interview Tips
- 💡Know both ways to create threads (extends Thread vs implements Runnable) and when to use each
- 💡Understand the difference between start() and run() - this is a common interview question
- 💡Be able to explain the thread lifecycle: NEW → RUNNABLE → RUNNING → TERMINATED
- 💡Know what join() does and why it's important for thread coordination
- 💡Understand that Thread.sleep() doesn't release locks (unlike wait())
- 💡Be prepared to write code showing both thread creation approaches