diff --git a/Report.md b/Report.md new file mode 100644 index 0000000..0966d72 --- /dev/null +++ b/Report.md @@ -0,0 +1,115 @@ +### 1. `start()` vs `run()` + +``` java +public class StartVsRun { + static class MyRunnable implements Runnable { + public void run() { + System.out.println("Running in: " + Thread.currentThread().getName()); + } + } + public static void main(String[] args) throws InterruptedException { + Thread t1 = new Thread(new MyRunnable(), "Thread-1"); + System.out.println("Calling run()"); + t1.run(); + Thread.sleep(100); + + Thread t2 = new Thread(new MyRunnable(), "Thread-2"); + System.out.println("Calling start()"); + t2.start(); + } +} +``` + +#### What output do you get from the program? Why? +- The output is: +``` +Calling run() +Running in: main +Calling start() +Running in: Thread-2 +``` +Since `t1.run()` executes the `run()` method directly inside the main thread, but `t2.start()` executes it inside the created new thread. + +#### What’s the difference in behavior between calling `start()` and `run()`? +- Calling `run()` is just a normal method call, so the program waits for it to finish before moving to the next line. + + Calling `start()` tells Java to create a new thread and execute the `run()` method inside it, which allows it to run alongside the main thread. + +--- +### 2. Daemon Threads + +``` java +public class DaemonExample { + static class DaemonRunnable implements Runnable { + public void run() { + for(int i = 0; i < 20; i++) { + System.out.println("Daemon thread running..."); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + //[Handling Exception...] + } + } + } + } + public static void main(String[] args) { + Thread thread = new Thread(new DaemonRunnable()); + thread.setDaemon(true); + thread.start(); + System.out.println("Main thread ends."); + } +} +``` + +#### What output do you get from the program? Why? +- The output is: +``` +Main thread ends. +Daemon thread running... +``` +Since `thread.setDaemon(true)` marks the thread as a background "daemon" thread, the program ends as soon as all user (non-daemon) threads (the main thread in this case) have finished, regardless of whether the daemon thread has completed its task. + +#### What happens if you remove `thread.setDaemon(true)`? +- The program continues printing `Daemon thread running...` 20 times even after the main thread has finished. +``` +Main thread ends. +Daemon thread running... +. +. +. +Daemon thread running... +``` + +#### What are some real-life use cases of daemon threads? +- In general, when we want a background task to run **only** while the main program is running, we use daemon threads. + + For example, background tasks like cache clearing, checking connectivity to a server, or monitoring CPU/Memory usage can all be done using daemon threads. + +--- +### 3. A shorter way to create threads + +``` java +public class ThreadDemo { + public static void main(String[] args) { + Thread thread = new Thread(() -> { + System.out.println("Thread is running using a ...!"); + }); + + thread.start(); + } +} +``` + +#### What output do you get from the program? +- The output is: +``` +Thread is running using a ...! +``` + +#### What is the () -> { ... } syntax called? +- It's called a lambda expression + +#### How is this code different from creating a class that extends Thread or implements Runnable? +- It’s much shorter and easier to read. Instead of having to define a new custom thread class (by extending Thread) +or separate the task from the thread itself (by implementing Runnable) just to run a few lines of code, +a lambda expression lets us pass the code directly to the thread. diff --git a/src/main/java/ChunkStatus.java b/src/main/java/ChunkStatus.java index 3e902dc..e088a95 100644 --- a/src/main/java/ChunkStatus.java +++ b/src/main/java/ChunkStatus.java @@ -1,12 +1,3 @@ -// Educational simplification: -// each worker writes only to its own ChunkStatus, -// and the monitor only reads chunk states. -// volatile is used here to make progress updates more visible across threads. - -/** - * Represents the state and download progress of a single file chunk. - * Each worker thread updates its own ChunkStatus, while the monitor thread reads it. - */ public class ChunkStatus { private final int chunkId; private final double chunkSizeMB; @@ -15,10 +6,7 @@ public class ChunkStatus { private volatile long startTimeMs; private volatile long endTimeMs; - /** - * Initializes a chunk with its unique ID and total allocated size. - * Progress-related fields are initialized to default values. - */ + public ChunkStatus(int chunkId, double chunkSizeMB) { this.chunkId = chunkId; this.chunkSizeMB = chunkSizeMB; @@ -28,7 +16,7 @@ public class ChunkStatus { this.endTimeMs = 0; } - // Getters and Setters + public int getChunkId() { return chunkId; } @@ -42,7 +30,6 @@ public class ChunkStatus { } public void setDownloadedMB(double downloadedMB) { - // Guard to prevent downloaded size exceeding actual chunk size if (downloadedMB >= this.chunkSizeMB) { this.downloadedMB = this.chunkSizeMB; } else { @@ -74,10 +61,7 @@ public class ChunkStatus { this.endTimeMs = endTimeMs; } - /** - * Helper method to calculate the duration of this specific chunk's download. - * Returns 0 if the chunk hasn't started or finished yet. - */ + public long getDownloadDurationMs() { if (startTimeMs > 0 && endTimeMs > startTimeMs) { return endTimeMs - startTimeMs; @@ -87,9 +71,7 @@ public class ChunkStatus { return 0; } - /** - * Helper method to calculate the download percentage of this chunk. - */ + public double getProgressPercentage() { if (chunkSizeMB == 0) return 100.0; return (downloadedMB / chunkSizeMB) * 100.0; diff --git a/src/main/java/ChunkUtils.java b/src/main/java/ChunkUtils.java index 8a4a07c..b3b540d 100644 --- a/src/main/java/ChunkUtils.java +++ b/src/main/java/ChunkUtils.java @@ -1,24 +1,14 @@ import java.util.ArrayList; import java.util.List; -/** - * Utility methods related to chunk creation. - */ + public class ChunkUtils { private ChunkUtils() { - // Utility class + } - /** - * Splits the total file size into a list of chunks. - * Chunk sizes are as even as possible. - * If the size is not exactly divisible, the last chunk gets the remainder. - * - * @param totalSizeMB total file size in MB - * @param chunkCount number of chunks - * @return list of ChunkStatus objects - */ + public static List createChunks(int totalSizeMB, int chunkCount) { List chunks = new ArrayList<>(); @@ -36,8 +26,7 @@ public class ChunkUtils { chunkSize += remainder; } - // Create one ChunkStatus object for each chunk. - // chunkId is the chunk number, and chunkSize is this chunk's total size. + chunks.add(new ChunkStatus(i + 1, chunkSize)); } diff --git a/src/main/java/ConfigReader.java b/src/main/java/ConfigReader.java index 5754060..eaf0cf3 100644 --- a/src/main/java/ConfigReader.java +++ b/src/main/java/ConfigReader.java @@ -63,7 +63,6 @@ public class ConfigReader { maxStepDownloadMB = Double.parseDouble(value); break; default: - // Ignore unknown keys to keep parsing simple break; } } diff --git a/src/main/java/DownloadConfig.java b/src/main/java/DownloadConfig.java index dc1ba3a..9e3e8a1 100644 --- a/src/main/java/DownloadConfig.java +++ b/src/main/java/DownloadConfig.java @@ -1,7 +1,3 @@ -/** - * Represents the configuration parameters for the simulated download manager. - * This class is immutable to ensure thread-safety when shared among worker threads. - */ public class DownloadConfig { private final String fileName; private final int totalSizeMB; @@ -11,9 +7,7 @@ public class DownloadConfig { private final double minStepDownloadMB; private final double maxStepDownloadMB; - /** - * Constructs a new DownloadConfig with specified simulation parameters. - */ + public DownloadConfig(String fileName, int totalSizeMB, int chunkCount, int minStepDelayMs, int maxStepDelayMs, double minStepDownloadMB, double maxStepDownloadMB) { @@ -26,7 +20,7 @@ public class DownloadConfig { this.maxStepDownloadMB = maxStepDownloadMB; } - // Getters + public String getFileName() { return fileName; } diff --git a/src/main/java/DownloadWorker.java b/src/main/java/DownloadWorker.java index 546de2d..579e980 100644 --- a/src/main/java/DownloadWorker.java +++ b/src/main/java/DownloadWorker.java @@ -1,12 +1,6 @@ import java.util.Random; -/** - * Simulates downloading a single chunk of a file. - * - *

This class is intentionally provided as a skeleton for students. - * The main multithreading and simulation logic should be completed - * in the run() method.

- */ + public class DownloadWorker implements Runnable { private final ChunkStatus chunkStatus; @@ -21,23 +15,51 @@ public class DownloadWorker implements Runnable { @Override public void run() { - // TODO: Record the chunk start time in chunkStatus. + chunkStatus.setStartTimeMs(System.currentTimeMillis()); double downloaded = 0.0; - // TODO: Print a message that this chunk has started downloading. + System.out.println("[" + Thread.currentThread().getName() + "] Started downloading Chunk #" + + chunkStatus.getChunkId() + " (Size: " + chunkStatus.getChunkSizeMB() + " MB)"); while (downloaded < chunkStatus.getChunkSizeMB()) { - // TODO: Generate a random sleep delay between min and max delay. - // TODO: Sleep for that delay. - // TODO: Generate a random download amount for this step. - // TODO: Increase downloaded, but do not go beyond chunk size. - // TODO: Save the updated downloaded value into chunkStatus. - // TODO: Optionally print step-by-step progress. + int minDelay = config.getMinStepDelayMs(); + int maxDelay = config.getMaxStepDelayMs(); + int delay = minDelay + random.nextInt(maxDelay - minDelay + 1); + + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + System.out.println("[" + Thread.currentThread().getName() + "] Chunk #" + + chunkStatus.getChunkId() + " was interrupted!"); + Thread.currentThread().interrupt(); + break; + } + + double minStep = config.getMinStepDownloadMB(); + double maxStep = config.getMaxStepDownloadMB(); + double stepDownload = minStep + (random.nextDouble() * (maxStep - minStep)); + + downloaded += stepDownload; + if (downloaded > chunkStatus.getChunkSizeMB()) { + downloaded = chunkStatus.getChunkSizeMB(); + } + + chunkStatus.setDownloadedMB(downloaded); + + System.out.printf("[%s] Chunk #%d progress: %.2f/%.1f MB (%.1f%%)%n", + Thread.currentThread().getName(), + chunkStatus.getChunkId(), + downloaded, + chunkStatus.getChunkSizeMB(), + chunkStatus.getProgressPercentage() + ); } - // TODO: Mark the chunk as completed. - // TODO: Record the chunk end time in chunkStatus. - // TODO: Print a message that this chunk has finished downloading. - } + chunkStatus.setCompleted(true); + chunkStatus.setEndTimeMs(System.currentTimeMillis()); + long totalTime = chunkStatus.getDownloadDurationMs(); + System.out.println("[" + Thread.currentThread().getName() + "] Finished Chunk #" + + chunkStatus.getChunkId() + " in " + totalTime + " ms!"); + } } diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 82bd239..fa14261 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -5,7 +5,6 @@ public class Main { public static void main(String[] args) { System.out.println("=== Simulated Download Manager ==="); - // 1. Read config DownloadConfig config; try { config = ConfigReader.readConfig("download_config.txt"); @@ -19,13 +18,11 @@ public class Main { System.out.println("Chunk count: " + config.getChunkCount()); System.out.println(); - // 2. Create chunks List chunks = ChunkUtils.createChunks( config.getTotalSizeMB(), config.getChunkCount() ); - // 3. Create worker threads List workerThreads = new ArrayList<>(); for (ChunkStatus chunk : chunks) { @@ -34,50 +31,30 @@ public class Main { workerThreads.add(workerThread); - // TODO: - // Students may print helpful debug information here, - // for example which chunk is assigned to which worker thread. + System.out.println("[Setup] Created " + workerThread.getName() + " for Chunk #" + + chunk.getChunkId() + " (Size: " + chunk.getChunkSizeMB() + " MB)"); } - // 4. Create and start monitor thread ProgressMonitor monitor = new ProgressMonitor(config, chunks); Thread monitorThread = new Thread(monitor, "Progress-Monitor"); - // TODO: - // Start the monitor thread before starting the workers - // so that progress can be displayed while downloading happens. - // - // Example idea: - // monitorThread.start(); + monitorThread.start(); - // 5. Start worker threads - // TODO: - // Start each worker thread in workerThreads. - // Use a loop and call start() on each thread. + for (Thread thread : workerThreads) { + thread.start(); + } - // 6. Wait for workers to finish - // TODO: - // Wait for all worker threads to complete by calling join(). - // This should be done inside a try-catch block for InterruptedException. - // - // Hint: - // for (Thread thread : workerThreads) { - // thread.join(); - // } - // TODO: - // After all workers finish, the monitor thread may also need to stop. - // Depending on how ProgressMonitor is implemented, students may: - // - wait for it to finish on its own, or - // - add a stopping mechanism in ProgressMonitor later. - // - // If your monitor finishes automatically, you may join it here. + try { + for (Thread thread : workerThreads) { + thread.join(); + } + monitorThread.join(); + } catch (InterruptedException e) { + System.out.println("[Main] Error: The main monitor thread was interrupted."); + Thread.currentThread().interrupt(); + } - // NOTE: - // this final report may show 0 progress because no worker has actually run yet. - // Until students complete the thread start/join TODOs above, - - // 7. Print final report System.out.println(); System.out.println("=== Final Report ==="); @@ -101,7 +78,7 @@ public class Main { System.out.println(); System.out.println("Completed chunks: " + completedChunks + "/" + chunks.size()); - System.out.println("Downloaded total: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB"); + System.out.println("Total downloaded: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB"); System.out.println("Simulation finished."); } } diff --git a/src/main/java/ProgressMonitor.java b/src/main/java/ProgressMonitor.java index 475c0e0..b12a991 100644 --- a/src/main/java/ProgressMonitor.java +++ b/src/main/java/ProgressMonitor.java @@ -16,16 +16,6 @@ public class ProgressMonitor implements Runnable { @Override public void run() { - // TODO: - // Repeatedly check chunk progress until all chunks are completed. - // In each loop: - // 1. Read the downloaded size from every chunk - // 2. Add all downloaded amounts to totalDownloadedMB - // 3. Count completed chunks - // 4. Print a progress message - // 5. If completedChunks == chunks.size(), print a final monitor message and stop - // 6. Otherwise sleep for monitorDelayMs and continue - while (true) { double totalDownloadedMB = 0.0; @@ -54,14 +44,16 @@ public class ProgressMonitor implements Runnable { chunks.size() ); - - // TODO: - // If all chunks are completed, print a final message and exit the loop + if (completedChunks == chunks.size()) { + System.out.printf("[Monitor] Download complete for %s! Total size: %d MB.%n", fileName, totalSizeMB); + break; + } try { Thread.sleep(monitorDelayMs); } catch (InterruptedException e) { System.out.println("Progress monitor interrupted."); + Thread.currentThread().interrupt(); return; } }