Lesson 5
Building a Simple Multithreaded Application
Building a Simple Multithreaded Application

Welcome back to our journey into Java concurrency! So far, you've learned about creating threads, managing their lifecycle, and synchronizing shared data. Today, we will apply these concepts by building a simple multithreaded application. This project will give you practical experience with real-world concurrency in Java and demonstrate how to use synchronization effectively for safe data sharing.

What You'll Learn

In this project, you will:

  • Implement a class that simulates a file download operation.
  • Create and manage multiple threads to perform concurrent downloads.
  • Use thread synchronization to manage output and prevent message overlap.
  • Set thread priorities and observe their impact on thread scheduling.

By the end of this project, you'll have the skills to build a basic multithreaded application and understand key concurrency concepts in a practical setting.

Project Overview: Multithreaded File Downloader

You will create a Downloader class that simulates file downloads. The class will implement the Runnable interface, allowing it to be run by multiple threads. A Main class will manage these threads and coordinate their completion.

Implementing the Downloader Class

First, we'll create a Downloader class to simulate the downloading process. This class will implement the Runnable interface to be run within threads. We will split its implementation for clarity.

Fields and Constructor
Java
1import java.util.Random; 2 3public class Downloader implements Runnable { 4 private final String fileName; 5 private final Random rnd; 6 7 public Downloader(String fileName) { 8 this.fileName = fileName; 9 this.rnd = new Random(); 10 } 11}

In this snippet, we declare the class fields: fileName, which holds the name of the file to be downloaded, and rnd, a Random object used to simulate variable download times. The constructor initializes these fields, setting up the Downloader instance with the specified file name.

Implementing the run() Method
Java
1@Override 2public void run() { 3 try { 4 synchronized (System.out) { 5 System.out.println(Thread.currentThread().getName() + " - Starting download: " + fileName); 6 } 7 Thread.sleep(rnd.nextInt(100) + 100); // Simulate time taken to download 8 synchronized (System.out) { 9 System.out.println(Thread.currentThread().getName() + " - Completed download: " + fileName); 10 } 11 } catch (InterruptedException e) { 12 synchronized (System.out) { 13 System.out.println(Thread.currentThread().getName() + " - Download interrupted: " + fileName); 14 } 15 } 16}

The run method implements the logic for simulating a file download. It uses a synchronized block to ensure that the start and completion messages for a download operation do not overlap with other threads' output, making the console output clear. The Thread.sleep() call simulates the time taken to download a file, using a random duration to mimic real-world variability. In case of interruption, an appropriate message is displayed to indicate that the download was interrupted.

Step 2: Managing Downloader Threads

Next, we'll create a Main class to manage multiple downloader threads. We'll start the threads, set their priorities, and ensure that the main thread waits for their completion.

Java
1public class Main { 2 public static void main(String[] args) throws InterruptedException { 3 // Creating downloader threads 4 Thread t1 = new Thread(new Downloader("file1.txt")); 5 Thread t2 = new Thread(new Downloader("file2.txt")); 6 Thread t3 = new Thread(new Downloader("file3.txt")); 7 8 // Setting thread names for better identification 9 t1.setName("Downloader-1"); 10 t2.setName("Downloader-2"); 11 t3.setName("Downloader-3"); 12 13 // Setting different priorities to illustrate effect on scheduling (optional, depends on JVM) 14 t1.setPriority(Thread.MAX_PRIORITY); 15 t2.setPriority(Thread.NORM_PRIORITY); 16 t3.setPriority(Thread.MIN_PRIORITY); 17 18 // Start all threads 19 t1.start(); 20 t2.start(); 21 t3.start(); 22 23 // Use join to ensure main thread waits for all download threads to finish 24 t1.join(); 25 t2.join(); 26 t3.join(); 27 28 // Indicate that all downloads are complete 29 System.out.println("All downloads completed"); 30 } 31}

The Main class manages multiple downloader threads, each downloading a different file. The threads are given unique names for better identification, and we set different priorities for each thread to illustrate scheduling differences. Finally, we use join() to ensure that the main thread waits for all download threads to complete before continuing, providing clear, sequential output.

Importance of Multithreaded Applications

Multithreaded applications are crucial in modern software development for several reasons:

  • Improved Performance: By parallelizing tasks, applications can make better use of system resources and complete operations faster.
  • Responsiveness: In user applications, multithreading keeps the interface responsive while performing background tasks.
  • Scalability: Concurrent processing is essential for handling multiple tasks simultaneously, such as serving web requests or processing transactions.

Understanding how to create and manage threads is a fundamental skill for any Java programmer. It allows you to build efficient, scalable, and responsive applications that can handle the demands of real-world scenarios.

You have now successfully built a simple multithreaded application in Java, applying key concurrency concepts you have learned. Ready to tackle more complex concurrency challenges in future lessons? Let's continue to deepen your knowledge and skills in Java concurrency!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.