Skip to main content

10.5 Real-World Applications

Java Multithreading: Real-World Applications

Overview

In this section, we’ll explore practical examples that showcase the power of Java multithreading concepts in real-world applications. From creating threads to handling thread safety and managing concurrent tasks, these examples demonstrate how to use multithreading effectively to optimize performance and responsiveness.


1. Web Server Request Handling

A multithreaded web server can handle multiple client requests simultaneously, improving response time and user experience. Each client request can be handled by a separate thread, allowing the server to respond to multiple users concurrently.

Example: Handling Client Requests with Executors

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WebServer {
private final ExecutorService executor = Executors.newFixedThreadPool(10); // Pool of 10 threads

public void handleRequest(Runnable request) {
executor.submit(request); // Assign request to a thread in the pool
}

public void shutdown() {
executor.shutdown(); // Shutdown the server
}

public static void main(String[] args) {
WebServer server = new WebServer();

// Simulating multiple client requests
for (int i = 1; i <= 5; i++) {
server.handleRequest(() -> System.out.println("Handling request on " + Thread.currentThread().getName()));
}

server.shutdown();
}
}

In this example, the web server uses a fixed thread pool to handle multiple client requests, each running on a separate thread to improve server response.


2. Stock Price Updater

A stock price updating application can use multithreading to fetch data from various APIs concurrently, ensuring fast and real-time updates. Each API call can run in a separate thread, and the data can be synchronized when updating a shared data store.

Example: Updating Stock Prices with Callable and Future

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class StockUpdater {
private final ExecutorService executor = Executors.newFixedThreadPool(3);

public Future<Double> fetchPrice(String stock) {
return executor.submit(() -> {
Thread.sleep(1000); // Simulate network delay
return Math.random() * 100; // Mock stock price
});
}

public static void main(String[] args) throws Exception {
StockUpdater updater = new StockUpdater();

Future<Double> price1 = updater.fetchPrice("AAPL");
Future<Double> price2 = updater.fetchPrice("GOOGL");
Future<Double> price3 = updater.fetchPrice("AMZN");

System.out.println("AAPL Price: " + price1.get());
System.out.println("GOOGL Price: " + price2.get());
System.out.println("AMZN Price: " + price3.get());

updater.executor.shutdown();
}
}

In this example, each stock price fetch operation runs in parallel, and the application waits for the result using Future.get().


3. Inventory Management System

An inventory management system requires thread-safe operations to handle multiple transactions (sales, restocking) on a shared inventory. Using synchronized or Lock ensures that updates to inventory quantities remain consistent.

Example: Thread-Safe Inventory Update with Lock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Inventory {
private int stock = 100;
private final Lock lock = new ReentrantLock();

public void updateStock(int quantity) {
lock.lock();
try {
stock += quantity;
System.out.println("Updated stock: " + stock);
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
Inventory inventory = new Inventory();

Thread sale = new Thread(() -> inventory.updateStock(-10)); // Sale reduces stock
Thread restock = new Thread(() -> inventory.updateStock(20)); // Restocking increases stock

sale.start();
restock.start();
}
}

This example uses a ReentrantLock to ensure thread-safe updates to the stock variable, preventing inconsistencies during simultaneous stock updates.


4. Data Processing with CountdownLatch

A data processing application can use CountDownLatch to ensure that multiple threads complete specific tasks before proceeding. This approach is useful when processing data in stages.

Example: Data Processing with CountdownLatch

import java.util.concurrent.CountDownLatch;

public class DataProcessor {
private final CountDownLatch latch = new CountDownLatch(3);

public void processStage1() {
new Thread(() -> {
System.out.println("Processing Stage 1...");
latch.countDown();
}).start();
}

public void processStage2() {
new Thread(() -> {
System.out.println("Processing Stage 2...");
latch.countDown();
}).start();
}

public void processStage3() {
new Thread(() -> {
System.out.println("Processing Stage 3...");
latch.countDown();
}).start();
}

public void completeProcessing() throws InterruptedException {
latch.await(); // Wait until all stages are processed
System.out.println("All stages completed. Finalizing data.");
}

public static void main(String[] args) throws InterruptedException {
DataProcessor processor = new DataProcessor();

processor.processStage1();
processor.processStage2();
processor.processStage3();

processor.completeProcessing();
}
}

In this example, CountDownLatch ensures all data processing stages complete before finalizing.


Summary

These real-world applications demonstrate the power of multithreading and concurrency in improving application performance and reliability:

  • Web Server Request Handling: Manages multiple client requests with a thread pool.
  • Stock Price Updater: Fetches real-time data using Callable and Future for asynchronous results.
  • Inventory Management: Ensures thread-safe operations with Lock.
  • Data Processing Pipeline: Uses CountDownLatch to coordinate multistage processing.

By applying these techniques, you can build efficient, responsive, and thread-safe applications in Java.