From dc9839c911eb8232944bc0123c9acb16b3daf275 Mon Sep 17 00:00:00 2001 From: Rezak Date: Fri, 5 Jun 2026 14:06:29 +0330 Subject: [PATCH] complete --- .idea/misc.xml | 2 +- Report.md | 63 ++++++++++++++++++++++++++++++ src/main/java/DownloadWorker.java | 39 +++++++++--------- src/main/java/Main.java | 58 +++++++++------------------ src/main/java/ProgressMonitor.java | 40 ++++++++++++------- 5 files changed, 127 insertions(+), 75 deletions(-) create mode 100644 Report.md diff --git a/.idea/misc.xml b/.idea/misc.xml index fdc35ea..0c04b52 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/Report.md b/Report.md new file mode 100644 index 0000000..fd8b556 --- /dev/null +++ b/Report.md @@ -0,0 +1,63 @@ +## Answers to Theoretical Questions + +### 1. `start()` vs `run()` + +**Output:** +``` +Calling run() +Running in: main +Calling start() +Running in: Thread-2 +``` + +- When we call `run()` directly, it's just a normal method call. + No new thread is created – everything happens inside the current thread (here `main`). + That's why the printed thread name is `main`. + +- Calling `start()` actually creates a brand new thread and then runs `run()` inside that new thread. + So the output shows that `Thread-2` is the one executing the code. + +- The main difference: + `run()` → synchronous, runs in the caller thread. + `start()` → asynchronous, spawns a separate thread and runs `run()` there. + +--- + +### 2. Daemon Threads + +**Program output (when `setDaemon(true)` is used):** +``` +Main thread ends. +Daemon thread running... +``` +(Only a few lines might appear before the program exits.) + +- By marking the thread as a daemon, we tell the JVM: *"This thread is doing background work – don't wait for it."* + As soon as all **user threads** finish, the JVM shuts down, even if daemon threads are still running. + Here `main` is a user thread, so after it prints its message and ends, the program terminates quickly. + +- If we **remove** `thread.setDaemon(true)`, the thread becomes a normal user thread. + Then the JVM will wait for it to finish all 20 iterations before exiting. + We'd see `"Daemon thread running..."` printed 20 times. + +- Real‑life examples of daemon threads: + - The **garbage collector** in Java – runs in the background but won't stop the JVM from closing. + - **Auto‑save** in a text editor – saves periodically but shouldn't block the program from closing. + +--- + +### 3. A shorter way to create threads + +**Output:** +``` +Thread is running using a ...! +``` + +- The `() -> { ... }` part is a **lambda expression**. + It's a compact way to write an anonymous function. + +- Instead of creating a whole new class that implements `Runnable` (or extending `Thread`), we can just pass the body of `run()` directly inside `new Thread(...)`. + This makes the code much shorter and easier to read, especially for one‑off tasks. + +- Lambdas are perfect for simple jobs, but if the task is complex or needs to be reused, a separate class might be better. + diff --git a/src/main/java/DownloadWorker.java b/src/main/java/DownloadWorker.java index 546de2d..65ccaba 100644 --- a/src/main/java/DownloadWorker.java +++ b/src/main/java/DownloadWorker.java @@ -1,12 +1,5 @@ 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 +14,29 @@ public class DownloadWorker implements Runnable { @Override public void run() { - // TODO: Record the chunk start time in chunkStatus. + chunkStatus.setStartTimeMs(System.currentTimeMillis()); + System.out.println("Chunk " + chunkStatus.getChunkId() + " started downloading."); double downloaded = 0.0; + double chunkSize = chunkStatus.getChunkSizeMB(); - // TODO: Print a message that this chunk has started downloading. + while (downloaded < chunkSize) { + int delay = config.getMinStepDelayMs() + + random.nextInt(config.getMaxStepDelayMs() - config.getMinStepDelayMs() + 1); + try { + Thread.sleep(delay); + } + catch (InterruptedException e) { + break; + } + double step = config.getMinStepDownloadMB() + + random.nextDouble() * (config.getMaxStepDownloadMB() - config.getMinStepDownloadMB()); - 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. + downloaded = Math.min(downloaded + step, chunkSize); + chunkStatus.setDownloadedMB(downloaded); } - - // 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()); + System.out.println("Chunk " + chunkStatus.getChunkId() + " finished downloading."); } } diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 82bd239..d9835b4 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"); @@ -18,14 +17,13 @@ public class Main { System.out.println("Total size (MB): " + config.getTotalSizeMB()); System.out.println("Chunk count: " + config.getChunkCount()); System.out.println(); + System.out.println("Starting download...\n"); - // 2. Create chunks List chunks = ChunkUtils.createChunks( config.getTotalSizeMB(), config.getChunkCount() ); - // 3. Create worker threads List workerThreads = new ArrayList<>(); for (ChunkStatus chunk : chunks) { @@ -33,51 +31,32 @@ public class Main { Thread workerThread = new Thread(worker, "Worker-" + chunk.getChunkId()); workerThreads.add(workerThread); - - // TODO: - // Students may print helpful debug information here, - // for example which chunk is assigned to which worker thread. + System.out.println("Assigned: Worker-" + chunk.getChunkId() + " -> Chunk " + chunk.getChunkId()); } - // 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 t : workerThreads) { + t.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(); - // } + for (Thread t : workerThreads) { + try { + t.join(); + } catch (InterruptedException e) { + System.out.println("Main thread interrupted while waiting for workers."); + } + } - // 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 { + monitorThread.join(); + } catch (InterruptedException e) { + System.out.println("Main thread interrupted while waiting for monitor."); + } - // 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 ==="); @@ -103,5 +82,6 @@ public class Main { System.out.println("Completed chunks: " + completedChunks + "/" + chunks.size()); System.out.println("Downloaded total: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB"); System.out.println("Simulation finished."); + System.out.println("Thank you for using the download manager!"); } } diff --git a/src/main/java/ProgressMonitor.java b/src/main/java/ProgressMonitor.java index 475c0e0..183a0c2 100644 --- a/src/main/java/ProgressMonitor.java +++ b/src/main/java/ProgressMonitor.java @@ -6,26 +6,19 @@ public class ProgressMonitor implements Runnable { private final int totalSizeMB; private final List chunks; private final long monitorDelayMs; + private final long startTimeMs; public ProgressMonitor(DownloadConfig config, List chunks) { this.fileName = config.getFileName(); this.totalSizeMB = config.getTotalSizeMB(); this.chunks = chunks; this.monitorDelayMs = 500; + this.startTimeMs = System.currentTimeMillis(); } @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 - + System.out.println("=== Download Progress ==="); while (true) { double totalDownloadedMB = 0.0; @@ -44,19 +37,36 @@ public class ProgressMonitor implements Runnable { percent = (totalDownloadedMB * 100.0) / totalSizeMB; } - System.out.printf( - "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", + long elapsedMs = System.currentTimeMillis() - startTimeMs; + double elapsedSec = elapsedMs / 1000.0; + double speedMBps = (elapsedSec > 0) ? (totalDownloadedMB / elapsedSec) : 0.0; + double remainingMB = totalSizeMB - totalDownloadedMB; + double etaSec = (speedMBps > 0) ? (remainingMB / speedMBps) : 0.0; + + int barLength = 30; + int filled = (int) (percent / 100.0 * barLength); + StringBuilder bar = new StringBuilder("["); + for (int i = 0; i < barLength; i++) { + bar.append(i < filled ? "=" : " "); + } + bar.append("]"); + + System.out.printf("\r%s %s %.1f/%.1f MB (%.1f%%) | Speed: %.1f MB/s | ETA: %.0fs | Chunks: %d/%d", + bar.toString(), fileName, totalDownloadedMB, (double) totalSizeMB, percent, + speedMBps, + etaSec, completedChunks, chunks.size() ); - - // TODO: - // If all chunks are completed, print a final message and exit the loop + if (completedChunks == chunks.size()) { + System.out.println("Monitor: All chunks completed."); + break; + } try { Thread.sleep(monitorDelayMs);