Multithreading in Java is a core mechanism that allows for the concurrent execution of two or more parts of a program to maximize CPU utilization. Each part, known as a thread, operates independently within the program, enabling complex applications to perform multiple tasks simultaneously.
Creating Threads in Java
There are two primary ways to create a thread in Java:
- By Extending the Thread Class: This involves creating a new class that extends the
Thread
class and overrides itsrun()
method. Therun()
method defines the code executed by the thread. - By Implementing the Runnable Interface: This approach requires creating a new class that implements the
Runnable
interface and defining therun()
method. An instance of this class is then passed to aThread
object, which starts the thread.
Example 1: Extending the Thread Class
Creating and starting threads by subclassing Thread
and overriding its run()
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class MyThread extends Thread { // The run method contains the code that will be executed by the thread. public void run() { // Prints the ID of the currently executing thread. System.out.println("Thread running: " + Thread.currentThread().getId()); } } public class ThreadExample { public static void main(String[] args) { MyThread thread1 = new MyThread(); // Creating the first thread thread1.start(); // Starting the first thread MyThread thread2 = new MyThread(); // Creating the second thread thread2.start(); // Starting the second thread } } // Expected output (Note: Thread IDs are exemplary and will vary): // Thread running: 11 // Thread running: 12 |
Code Explanation: This example demonstrates creating two threads by extending the Thread
class. Each MyThread
instance overrides the run()
method, where custom behavior for the thread is defined. When start()
is called, the JVM calls the run()
method of these threads. The output shows the unique ID of each thread, illustrating that two separate threads are running concurrently.
Example 2: Instantiating and Starting Two Threads
Demonstration of concurrently running two threads using the Runnable
interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class SimpleThreadExample { public static void main(String[] args) { // Defines a Runnable task that prints numbers 1 to 5 Runnable task = () -> { for (int i = 1; i <= 5; i++) { // Prints the current loop index along with the executing thread's ID System.out.println("Thread " + Thread.currentThread().getId() + ": " + i); try { Thread.sleep(100); // Introduce a slight delay } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread thread1 = new Thread(task); // Creates the first thread with the task Thread thread2 = new Thread(task); // Creates the second thread with the same task thread1.start(); // Starts the first thread thread2.start(); // Starts the second thread } } // Expected output (Note: Thread IDs and interleaving will vary): // Thread 13: 1 // Thread 14: 1 // Thread 13: 2 // Thread 14: 2 // Thread 13: 3 // Thread 14: 3 // Thread 13: 4 // Thread 14: 4 // Thread 13: 5 // Thread 14: 5 |
Code Explanation: In this example, a Runnable
task is defined as a lambda expression that prints numbers 1 to 5, along with the current thread’s ID. Two Thread
objects are created with this task and started. The output demonstrates that both threads execute the same task concurrently, showcasing how to run parallel tasks using the Runnable
interface.
Synchronization in Multithreading
Synchronization ensures safe access to shared resources by multiple threads.
Example: Synchronization
Incrementing a shared counter with synchronized methods to avoid data inconsistency.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class Counter { private int count = 0; // Synchronized method to safely increment the counter public synchronized void increment() { count++; } public int getCount() { return count; } } public class SyncExample { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) counter.increment(); }); t1.start(); t2.start(); t1.join(); // Waits for t1 to finish t2.join(); // Waits for t2 to finish // Prints the final count, ensuring all increments are accounted for System.out.println("Count: " + counter.getCount()); } } // Expected output: // Count: 2000 |
Code Explanation: This synchronization example features a Counter
class with a method increment()
that increases a count. The method is marked synchronized
to ensure that only one thread can execute it at a time, preventing race conditions. Two threads increment the counter 1000 times each. Because of synchronization, the final count reliably reaches 2000, demonstrating thread-safe operations on shared resources.
Conclusion
Through these examples, we’ve explored fundamental concepts of Java multithreading, including thread creation, execution, and synchronization. The explanations aim to clarify how threads are managed and synchronized in Java, providing a foundation for building efficient, concurrent applications. Understanding these principles is crucial for leveraging the full capabilities of Java multithreading in real-world applications.