In Java, threads are a fundamental part of concurrency and multi-threading. A thread is a lightweight process that allows a program to perform multiple tasks simultaneously.
Java provides several ways to create and start a thread, which is useful for making applications more responsive and capable of handling multiple operations concurrently.
There are two main ways to start a thread in Java:
Extending the Thread class.
Implementing the Runnable interface.
Both approaches allow you to run code concurrently, but they differ in how they are implemented and in their flexibility.
1. Starting a Thread by Extending the Thread Class
One way to create a thread in Java is by extending the Thread class and overriding its run() method. The run() method contains the code that will be executed by the thread.
Example 1: Extending the Thread Class
class MyThread extends Thread { @Override public void run() { // Code that will be executed by the thread for (int i = 1; i <= 5; i++) { System.out.println("Thread: " + i); try { Thread.sleep(500); // Sleep for 500 milliseconds } catch (InterruptedException e) { System.out.println(e); } } } } public class ThreadExample1 { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Start the thread // Main thread continues for (int i = 1; i <= 5; i++) { System.out.println("Main: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } }
Output (might vary due to thread scheduling):
Thread: 1 Main: 1 Thread: 2 Main: 2 Thread: 3 Main: 3 Thread: 4 Main: 4 Thread: 5 Main: 5
Explanation:
In this example, the MyThread class extends Thread and overrides the run() method.
The start() method is called to initiate the thread, which internally calls the run() method.
Both the main thread and the newly created thread run concurrently.
2. Starting a Thread by Implementing the Runnable Interface
Another way to start a thread is by implementing the Runnable interface. This approach is more flexible because your class can extend another class, unlike the previous method where you are restricted to extending Thread.
Example 2: Implementing the Runnable Interface
class MyRunnable implements Runnable { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("Runnable: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } } public class ThreadExample2 { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); // Start the thread // Main thread continues for (int i = 1; i <= 5; i++) { System.out.println("Main: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } }
Output (might vary):
Runnable: 1 Main: 1 Runnable: 2 Main: 2 Runnable: 3 Main: 3 Runnable: 4 Main: 4 Runnable: 5 Main: 5
Explanation:
The MyRunnable class implements the Runnable interface and provides the implementation of the run() method.
A Thread object is created and passed the runnable object as an argument to its constructor.
The start() method starts the thread, which invokes the run() method of MyRunnable.
3. Using Lambda Expressions with Threads
In Java 8 and later, you can use lambda expressions to create a thread in a more concise way when using the Runnable interface. This is particularly useful for reducing boilerplate code.
Example 3: Using Lambda Expression
public class ThreadExample3 { public static void main(String[] args) { Runnable task = () -> { for (int i = 1; i <= 5; i++) { System.out.println("Lambda: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } }; Thread thread = new Thread(task); thread.start(); // Start the thread // Main thread continues for (int i = 1; i <= 5; i++) { System.out.println("Main: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } }
Output:
Lambda: 1 Main: 1 Lambda: 2 Main: 2 Lambda: 3 Main: 3 Lambda: 4 Main: 4 Lambda: 5 Main: 5
Explanation:
In this example, the Runnable task is defined using a lambda expression, making the code more concise.
This approach is a modern and convenient way to create threads in Java, especially for short and simple tasks.
4. Using the Thread.join() Method
The join() method allows one thread to wait for the completion of another. This can be useful when you want the main thread to wait until another thread finishes its work.
Example 4: Using Thread.join()
class MyTask implements Runnable { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("Task: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } } public class ThreadExample4 { public static void main(String[] args) { Thread thread = new Thread(new MyTask()); thread.start(); // Start the thread try { thread.join(); // Wait for the thread to finish } catch (InterruptedException e) { System.out.println(e); } // Now the main thread will execute System.out.println("Main thread completed after task."); } }
Output:
Task: 1 Task: 2 Task: 3 Task: 4 Task: 5 Main thread completed after task.
Explanation:
The thread.join() method makes the main thread wait for the thread to complete its execution before proceeding.
After the thread finishes, the main thread continues its execution.
5. Thread Priorities
Threads in Java can be assigned priorities. Java provides three priority levels:
Thread.MIN_PRIORITY (1)
Thread.NORM_PRIORITY (5) (default)
Thread.MAX_PRIORITY (10)
You can set a thread's priority using the setPriority() method, but keep in mind that thread priorities are only hints to the thread scheduler and may not be strictly followed by the JVM on all platforms.
Example 5: Setting Thread Priorities
class PriorityTask implements Runnable { private String name; public PriorityTask(String name) { this.name = name; } @Override public void run() { for (int i = 1; i <= 3; i++) { System.out.println(name + ": " + i); } } } public class ThreadExample5 { public static void main(String[] args) { Thread thread1 = new Thread(new PriorityTask("Low Priority")); Thread thread2 = new Thread(new PriorityTask("High Priority")); thread1.setPriority(Thread.MIN_PRIORITY); // Priority 1 thread2.setPriority(Thread.MAX_PRIORITY); // Priority 10 thread1.start(); thread2.start(); } }
Output:
High Priority: 1 High Priority: 2 High Priority: 3 Low Priority: 1 Low Priority: 2 Low Priority: 3
Explanation:
The thread with higher priority (thread2) is more likely to execute first, but thread scheduling is still dependent on the JVM and the operating system.
Setting priorities does not guarantee the order of execution.
6. Daemon Threads
A daemon thread is a low-priority thread that runs in the background to perform tasks like garbage collection. These threads do not prevent the JVM from exiting when all user threads (non-daemon threads) finish their execution.
Example 6: Daemon Thread
class DaemonTask implements Runnable { @Override public void run() { while (true) { System.out.println("Daemon thread running..."); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(e); } } } } public class ThreadExample6 { public static void main(String[] args) { Thread daemonThread = new Thread(new DaemonTask()); daemonThread.setDaemon(true); // Set this thread as daemon daemonThread.start(); try { Thread.sleep(2000); // Main thread sleeps for 2 seconds } catch (InterruptedException e) { System.out.println(e); } System.out.println("Main thread finished."); } }
Output:
Daemon thread running... Daemon thread running... Daemon thread running... Daemon thread running... Main thread finished.
Explanation:
The daemon thread keeps running, but as soon as the main thread (the non-daemon thread) finishes, the program terminates, and the daemon thread is also stopped automatically.
Daemon threads are typically used for background tasks like garbage collection.
Conclusion
Java provides a variety of ways to create and manage threads, from extending the Thread class to implementing the Runnable interface.
You can control thread behavior using features like join(), priorities, and daemon threads. Understanding how to use threads effectively is crucial for building efficient, concurrent applications.
Key takeaways:
Extending Thread or implementing Runnable are two primary ways to start threads.
Use lambda expressions for concise code in modern Java.
Use join() to make one thread wait for another to finish.
Control thread execution order with priorities (though it's only a hint).
Use daemon threads for background tasks that should not prevent the JVM from exiting.