Multithreading

Section 6: Multithreading

Lesson 1: Introduction to Multithreading

1.1 Creating and Running Threads

Multithreading allows the execution of multiple threads concurrently, enabling better utilization of system resources. In Java, you can create threads by extending the Thread class or implementing the Runnable interface.

Example (Extending Thread Class):

class MyThread extends Thread {

    public void run() {

        // Code to be executed in the thread

        for (int i = 1; i <= 5; i++) {

            System.out.println(Thread.currentThread().getId() + " - Value " + i);

        }

    }

}


public class ThreadExample {

    public static void main(String[] args) {

        // Creating and starting threads

        MyThread thread1 = new MyThread();

        MyThread thread2 = new MyThread();


        thread1.start();

        thread2.start();

    }

}

Example (Implementing Runnable Interface): 

class MyRunnable implements Runnable {

    public void run() {

        // Code to be executed in the thread

        for (int i = 1; i <= 5; i++) {

            System.out.println(Thread.currentThread().getId() + " - Value " + i);

        }

    }

}


public class RunnableExample {

    public static void main(String[] args) {

        // Creating threads using the Runnable interface

        Thread thread1 = new Thread(new MyRunnable());

        Thread thread2 = new Thread(new MyRunnable());


        thread1.start();

        thread2.start();

    }

}

1.2 Synchronization and Thread Safety

Multithreading can introduce data inconsistency issues when multiple threads access shared resources simultaneously. Synchronization is used to ensure that only one thread can access a shared resource at a time.

Example (Synchronization): 

class Counter {

    private int count = 0;


    // Synchronized method

    public synchronized void increment() {

        count++;

    }


    public int getCount() {

        return count;

    }

}


public class SynchronizationExample {

    public static void main(String[] args) {

        Counter counter = new Counter();


        // Creating threads that increment the counter

        Thread thread1 = new Thread(() -> {

            for (int i = 0; i < 1000; i++) {

                counter.increment();

            }

        });


        Thread thread2 = new Thread(() -> {

            for (int i = 0; i < 1000; i++) {

                counter.increment();

            }

        });


        thread1.start();

        thread2.start();


        // Waiting for threads to finish

        try {

            thread1.join();

            thread2.join();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }


        // Displaying the final count

        System.out.println("Count: " + counter.getCount());

    }

}


Lesson 2: Concurrency Utilities

2.1 Java Executor Framework

The Java Executor framework provides a higher-level replacement for managing and controlling threads. It includes interfaces such as Executor, ExecutorService, and ScheduledExecutorService.

Example (Executor Framework): 

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


public class ExecutorFrameworkExample {

    public static void main(String[] args) {

        // Creating a fixed-size thread pool

        ExecutorService executor = Executors.newFixedThreadPool(2);


        // Submitting tasks to the executor

        for (int i = 0; i < 5; i++) {

            executor.submit(() -> {

                System.out.println("Thread ID: " + Thread.currentThread().getId());

            });

        }


        // Shutting down the executor

        executor.shutdown();

    }

}


2.2 Concurrent Collections

Concurrent collections provide thread-safe implementations of standard Java collections. These collections can be used in multithreaded environments without the need for explicit synchronization.

Example (ConcurrentHashMap):

import java.util.concurrent.ConcurrentHashMap;


public class ConcurrentHashMapExample {

    public static void main(String[] args) {

        // Creating a concurrent hash map

        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();


        // Putting key-value pairs

        map.put("One", 1);

        map.put("Two", 2);

        map.put("Three", 3);


        // Iterating through the map

        map.forEach((key, value) -> {

            System.out.println("Key: " + key + ", Value: " + value);

        });

    }

}

Multithreading is a powerful concept that can significantly enhance the performance of Java applications. Understanding how to create threads, handle synchronization, and leverage concurrency utilities is essential for developing efficient and scalable software. Practice these examples to solidify your understanding of multithreading in Java.