diff --git a/Report.md b/Report.md new file mode 100644 index 0000000..ad3fa36 --- /dev/null +++ b/Report.md @@ -0,0 +1,41 @@ +1: + the output is Calling run() + Running in: main + Calling start() + Running in: Thread-2 + +t1.run() does not start a new thread. +It just calls the run() method like a normal method call, and it executes on the current thread. +t2.start() creates a new thread and then that new thread executes run(), so current Thread Name becomes Thread-2. + +run() is just a normal method call, so it runs in the current thread. + +start() creates a new thread, and that new thread executes run(). + +2: + Output: + Main thread ends. + It may also print: + Daemon thread running… + a few times, or sometimes not at all. + Why: + Because the created thread is a daemon thread. Daemon threads run in the background, and when the main thread finishes, the JVM can stop immediately. + So the daemon thread may not complete its loop. + If thread.setDaemon(true) is removed: + Then the thread becomes a user thread (non-daemon thread). + and the JVM will wait for it to finish, so "Daemon thread running..." will print many times until the loops end. + Real-life uses of daemon threads: + Garbage collection + Background auto-save + Cache cleanup + Monitoring or logging services + Timer or scheduler tasks running in background +3: + What output do you get from the program? + Thread is running using a …! + What is the () -> { ... } syntax called? + This is called a lambda expression. + + How is this code different from creating a class that extends Thread or implements Runnable? + This is a shorter and cleaner way to write thread code. + Instead of creating a separate class, the task is written directly inside the lambda expression. diff --git a/src/main/java/.idea/.gitignore b/src/main/java/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/src/main/java/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/src/main/java/.idea/misc.xml b/src/main/java/.idea/misc.xml new file mode 100644 index 0000000..a20905f --- /dev/null +++ b/src/main/java/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/.idea/modules.xml b/src/main/java/.idea/modules.xml new file mode 100644 index 0000000..122a905 --- /dev/null +++ b/src/main/java/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/.idea/vcs.xml b/src/main/java/.idea/vcs.xml new file mode 100644 index 0000000..c2365ab --- /dev/null +++ b/src/main/java/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/DownloadWorker.java b/src/main/java/DownloadWorker.java index 546de2d..ccd6620 100644 --- a/src/main/java/DownloadWorker.java +++ b/src/main/java/DownloadWorker.java @@ -21,23 +21,33 @@ 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. - 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. - } + try { + int delay = config.getMinStepDelayMs() + + random.nextInt(config.getMaxStepDelayMs() - config.getMinStepDelayMs() + 1); + Thread.sleep(delay); - // TODO: Mark the chunk as completed. - // TODO: Record the chunk end time in chunkStatus. - // TODO: Print a message that this chunk has finished downloading. + double stepDownload = config.getMinStepDownloadMB() + + random.nextDouble() * (config.getMaxStepDownloadMB() - config.getMinStepDownloadMB()); + + downloaded += stepDownload; + if (downloaded > chunkStatus.getChunkSizeMB()) { + downloaded = chunkStatus.getChunkSizeMB(); + } + + chunkStatus.setDownloadedMB(downloaded); + + } catch (InterruptedException e) { + System.err.println("[Worker-" + chunkStatus.getChunkId() + "] Download interrupted."); + Thread.currentThread().interrupt(); + return; + } + } + chunkStatus.setCompleted(true); + chunkStatus.setEndTimeMs(System.currentTimeMillis()); } } diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 82bd239..ba09981 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -34,36 +34,34 @@ 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("Assigned 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 thread : workerThreads) + thread.start(); + + try { + for (Thread thread : workerThreads) { + thread.join(); + } + } catch (InterruptedException e) { + System.out.println("Interrupted."); + return; + } + + try { + monitorThread.join(); + } catch (InterruptedException e) { + System.out.println("Interrupted."); + return; + } - // 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. diff --git a/src/main/java/ProgressMonitor.java b/src/main/java/ProgressMonitor.java index 475c0e0..7cf598d 100644 --- a/src/main/java/ProgressMonitor.java +++ b/src/main/java/ProgressMonitor.java @@ -16,16 +16,7 @@ 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 - + long startTimeMs = System.currentTimeMillis(); while (true) { double totalDownloadedMB = 0.0; @@ -33,37 +24,72 @@ public class ProgressMonitor implements Runnable { for (ChunkStatus chunk : chunks) { totalDownloadedMB += chunk.getDownloadedMB(); - if (chunk.isCompleted()) { completedChunks++; } } - double percent = 0.0; - if (totalSizeMB > 0) { - percent = (totalDownloadedMB * 100.0) / totalSizeMB; - } + long elapsedMs = System.currentTimeMillis() - startTimeMs; + double elapsedSeconds = elapsedMs / 1000.0; + + double speedMBps = elapsedSeconds > 0.0 + ? totalDownloadedMB / elapsedSeconds + : 0.0; + + double percent = totalSizeMB > 0 + ? totalDownloadedMB * 100.0 / totalSizeMB + : 100.0; + percent = Math.max(0.0, Math.min(100.0, percent)); + + double remainingMB = Math.max(0.0, totalSizeMB - totalDownloadedMB); + long etaSeconds = speedMBps > 0.0 + ? (long) Math.ceil(remainingMB / speedMBps) + : 0L; + + int barWidth = 40; + int filled = (int) Math.round(percent / 100.0 * barWidth); + filled = Math.max(0, Math.min(barWidth, filled)); + + String bar = "#".repeat(filled) + "-".repeat(barWidth - filled); System.out.printf( - "Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", - fileName, - totalDownloadedMB, - (double) totalSizeMB, + "\r[%s] %6.2f%% %7.1f/%d MB | %6.2f MB/s | ETA %s | chunks %d/%d", + bar, percent, + totalDownloadedMB, + totalSizeMB, + speedMBps, + formatEta(etaSeconds), completedChunks, chunks.size() ); + System.out.flush(); - - // TODO: - // If all chunks are completed, print a final message and exit the loop + if (completedChunks == chunks.size()) { + System.out.println(); + System.out.println("All chunks completed."); + break; + } try { Thread.sleep(monitorDelayMs); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + System.out.println(); System.out.println("Progress monitor interrupted."); return; } } } + + private String formatEta(long totalSeconds) { + long minutes = totalSeconds / 60; + long seconds = totalSeconds % 60; + + if (minutes > 0) { + return String.format("%dm %02ds", minutes, seconds); + } + + return String.format("%ds", seconds); + } }