Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
219f008088 | ||
|
|
dd013d1c44 | ||
|
|
11c10175da | ||
|
|
fd4b232408 | ||
|
|
b2bf918030 | ||
|
|
bcb381f84b |
@@ -0,0 +1,95 @@
|
||||
### 1. `start()` vs `run()`
|
||||
|
||||
* #### output:
|
||||
```bash
|
||||
Calling run()
|
||||
Running in: main
|
||||
Calling start()
|
||||
Running in: Thread-2
|
||||
```
|
||||
|
||||
When we call `t1.run()`, Java does not create a new thread. This method runs like a normal method in the thread it was called on. That's why `Thread.currentThread().getName()` prints the name of the current thread, main.
|
||||
|
||||
But when we call `t2.start()`, the JVM creates a new thread in the background. JVM creates a new thread called Thread-2 and executes the `run()` method inside this new thread. That's why the resulting thread is named Thread-2.
|
||||
|
||||
* #### the difference in behavior between calling `start()` and `run()`
|
||||
|
||||
The main difference between these two methods:
|
||||
1. how they manage memory
|
||||
2. create new trades
|
||||
|
||||
`run()` method:
|
||||
* No new thread
|
||||
* Execution: Completely normal and line-by-line, The main thread of the program enters the run method, executes the code, and exits. Until this method finishes, subsequent lines of code in the main method wait.
|
||||
|
||||
`start()`
|
||||
* New thread
|
||||
* Execution: Completely parallel and asynchronous. As soon as you call `start()`, the new thread starts in the background, and the main thread immediately moves on to the next line of its code without waiting for the new thread to finish.
|
||||
|
||||
---
|
||||
|
||||
### 2. Daemon Threads
|
||||
|
||||
* #### output:
|
||||
```bash
|
||||
Main thread ends.
|
||||
Daemon thread running... (multiple times)
|
||||
```
|
||||
|
||||
Why?
|
||||
|
||||
In Java we have two types of threads: User Threads (user or main threads) and Daemon Threads (backup threads).
|
||||
|
||||
The Java Virtual Machine (JVM) continues to run as long as at least one user thread is alive. Once all user threads have finished, the JVM stops the application, even if daemon threads are still running.
|
||||
|
||||
In this code, the main thread is a user thread. When `thread.start()` is executed, the daemon thread starts running, but immediately on the next line, the main thread prints "Main thread ends." and terminates. Since no other user threads are alive, the JVM does not allow the daemon thread to complete its 20-thread loop; it immediately closes the entire program and the job stops.
|
||||
|
||||
* #### If we remove the line `thread.setDaemon(true)`
|
||||
|
||||
If we delete this line, this thread will become a user Thread and the program will no longer close immediately, and the output will be like this:
|
||||
|
||||
```bash
|
||||
Main thread ends.
|
||||
Daemon thread running...
|
||||
Daemon thread running...
|
||||
... (This phrase is printed exactly 20 times)
|
||||
Daemon thread running...
|
||||
```
|
||||
|
||||
* #### Real-life use cases
|
||||
|
||||
Daemon threads are typically used for tasks that are useful as long as the main application is open, but are no longer needed once the main application is closed.
|
||||
|
||||
Some real-world examples:
|
||||
|
||||
1. Garbage Collector: This trick runs continuously in the background to clear unused memory.
|
||||
2. Auto-Save: A daemon thread in the background that saves our writing every few minutes.
|
||||
3. Spell Checker: As we type, tread draws a red line in the background under misspelled words.
|
||||
|
||||
---
|
||||
|
||||
### 3. A shorter way to create threads
|
||||
|
||||
* #### output:
|
||||
```bash
|
||||
Thread is running using a ...!
|
||||
```
|
||||
|
||||
* #### The name of this `syntax () -> { ... }`
|
||||
|
||||
This structure is called a Lambda Expression in Java.
|
||||
|
||||
The open and close parentheses `()` represent the method inputs (which are empty since the run method takes no parameters in its input). The arrow symbol `->` means take these inputs and put them inside the code inside the curly braces `{ ... }` to be executed.
|
||||
|
||||
* #### The difference
|
||||
|
||||
In terms of final performance and CPU behavior, there is no difference; the operating system creates a new thread in both cases. But the differences:
|
||||
|
||||
1. Eliminate Boilerplate Code: In the old way, we had to create a new class, write `public void run()` inside it, and add a bunch of opening and closing braces. Lambda removes all of this formality and lets us get straight to the “boilerplate” (the code that needs to be executed).
|
||||
2. Using Functional Interface: Java is very smart. Since the Thread class constructor takes an input of type Runnable interface and this interface has only one method called `run()`, Java automatically understands that the code inside the lambda is going to replace that one `run()` method.
|
||||
3. Preserve inheritance: When we write a class that extends Thread, due to Java limitations, we cannot inherit that class from any other class. But the lambda method (which is based on Runnable) does not impose this limitation and leaves us free to design our classes.
|
||||
4. Separating the task from the executor: In this method, we inject the task into the execution engine (Thread) as a body of code, which is much more standard than merging the two together.
|
||||
|
||||
---
|
||||
|
||||
Done :)
|
||||
@@ -105,4 +105,8 @@ public class ChunkStatus {
|
||||
completed ? " [Completed]" : ""
|
||||
);
|
||||
}
|
||||
|
||||
public void setStartTime(long startTime) {
|
||||
this.startTimeMs = startTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public class ConfigReader {
|
||||
int maxStepDelayMs = 0;
|
||||
double minStepDownloadMB = 0;
|
||||
double maxStepDownloadMB = 0;
|
||||
int minDelayMs = 0;
|
||||
int maxDelayMs = 0;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
@@ -62,6 +64,12 @@ public class ConfigReader {
|
||||
case "maxStepDownloadMB":
|
||||
maxStepDownloadMB = Double.parseDouble(value);
|
||||
break;
|
||||
case "minDelayMs":
|
||||
minDelayMs = Integer.parseInt(value);
|
||||
break;
|
||||
case "maxDelayMs":
|
||||
maxDelayMs = Integer.parseInt(value);
|
||||
break;
|
||||
default:
|
||||
// Ignore unknown keys to keep parsing simple
|
||||
break;
|
||||
@@ -79,7 +87,9 @@ public class ConfigReader {
|
||||
minStepDelayMs,
|
||||
maxStepDelayMs,
|
||||
minStepDownloadMB,
|
||||
maxStepDownloadMB
|
||||
maxStepDownloadMB,
|
||||
minDelayMs,
|
||||
maxDelayMs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,15 @@ public class DownloadConfig {
|
||||
private final int maxStepDelayMs;
|
||||
private final double minStepDownloadMB;
|
||||
private final double maxStepDownloadMB;
|
||||
private final int maxDelayMs;
|
||||
private final int minDelayMs;
|
||||
|
||||
/**
|
||||
* Constructs a new DownloadConfig with specified simulation parameters.
|
||||
*/
|
||||
public DownloadConfig(String fileName, int totalSizeMB, int chunkCount,
|
||||
int minStepDelayMs, int maxStepDelayMs,
|
||||
double minStepDownloadMB, double maxStepDownloadMB) {
|
||||
double minStepDownloadMB, double maxStepDownloadMB, int maxDelayMs, int minDelayMs) {
|
||||
this.fileName = fileName;
|
||||
this.totalSizeMB = totalSizeMB;
|
||||
this.chunkCount = chunkCount;
|
||||
@@ -24,6 +26,8 @@ public class DownloadConfig {
|
||||
this.maxStepDelayMs = maxStepDelayMs;
|
||||
this.minStepDownloadMB = minStepDownloadMB;
|
||||
this.maxStepDownloadMB = maxStepDownloadMB;
|
||||
this.maxDelayMs = maxDelayMs;
|
||||
this.minDelayMs = minDelayMs;
|
||||
}
|
||||
|
||||
// Getters
|
||||
@@ -67,4 +71,12 @@ public class DownloadConfig {
|
||||
", maxStepDownloadMB=" + maxStepDownloadMB +
|
||||
'}';
|
||||
}
|
||||
|
||||
public int getMinDelayMs() {
|
||||
return minDelayMs;
|
||||
}
|
||||
|
||||
public int getMaxDelayMs() {
|
||||
return maxDelayMs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,23 +21,57 @@ public class DownloadWorker implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Record the chunk start time in chunkStatus.
|
||||
long startTime = System.currentTimeMillis();
|
||||
chunkStatus.setStartTime(startTime);
|
||||
|
||||
double downloaded = 0.0;
|
||||
|
||||
// TODO: Print a message that this chunk has started downloading.
|
||||
System.out.println("Chunk \"" + chunkStatus.getChunkId() + "\" 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 {
|
||||
//Generate a random sleep delay between min and max delay.
|
||||
int minDelay = config.getMinDelayMs();
|
||||
int maxDelay = config.getMaxDelayMs();
|
||||
int randomDelay = random.nextInt((maxDelay - minDelay) + 1) + minDelay;
|
||||
|
||||
//Sleep for that delay.
|
||||
Thread.sleep(randomDelay);
|
||||
|
||||
//Generate a random download amount for this step.
|
||||
double minStep = config.getMinStepDelayMs();
|
||||
double maxStep = config.getMaxStepDelayMs();
|
||||
double randomStep = (maxStep + minStep)*random.nextDouble() + minStep;
|
||||
|
||||
//Increase downloaded, but do not go beyond chunk size.
|
||||
downloaded += randomStep;
|
||||
if (downloaded > chunkStatus.getChunkSizeMB()) {
|
||||
downloaded = chunkStatus.getChunkSizeMB();
|
||||
}
|
||||
|
||||
//Save the updated downloaded value into chunkStatus.
|
||||
chunkStatus.setDownloadedMB(downloaded);
|
||||
|
||||
//print step-by-step progress.
|
||||
System.out.printf("Chunk #%d: Downloaded %.2f / %.2f MB%n",
|
||||
chunkStatus.getChunkId(), downloaded, chunkStatus.getChunkSizeMB());
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Chunk \"" + chunkStatus.getChunkId() + "\" was interrupted!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Mark the chunk as completed.
|
||||
// TODO: Record the chunk end time in chunkStatus.
|
||||
// TODO: Print a message that this chunk has finished downloading.
|
||||
//Mark the chunk as completed.
|
||||
chunkStatus.setCompleted(true);
|
||||
|
||||
//Record the chunk end time in chunkStatus.
|
||||
long endTime = System.currentTimeMillis();
|
||||
chunkStatus.setEndTimeMs(endTime);
|
||||
|
||||
//Print a message that this chunk has finished downloading.
|
||||
System.out.println("Chunk \"" + chunkStatus.getChunkId() + "\" has finished downloading.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+102
-50
@@ -1,5 +1,6 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
@@ -19,68 +20,122 @@ public class Main {
|
||||
System.out.println("Chunk count: " + config.getChunkCount());
|
||||
System.out.println();
|
||||
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.println("Select Download Mode:");
|
||||
System.out.println("1. Multithreaded Mode (Parallel Downloading)");
|
||||
System.out.println("2. Sequential Mode (One by One)");
|
||||
System.out.println("3. Run Performance Benchmark (Compare Both)");
|
||||
System.out.print("Your Choice (1-3): ");
|
||||
int choice = scanner.nextInt();
|
||||
|
||||
switch (choice) {
|
||||
case 1 -> {
|
||||
System.out.println("\n--- Starting Multithreaded Download ---");
|
||||
runMultithreaded(config);
|
||||
}
|
||||
case 2 -> {
|
||||
System.out.println("\n--- Starting Sequential Download ---");
|
||||
runSequential(config);
|
||||
}
|
||||
case 3 -> {
|
||||
System.out.println("\n--- Running Performance Benchmark ---");
|
||||
|
||||
System.out.println("\n[Test 1/2] Executing Sequential Mode...");
|
||||
long sequentialTime = runSequential(config);
|
||||
|
||||
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
|
||||
|
||||
System.out.println("\n[Test 2/2] Executing Multithreaded Mode...");
|
||||
long multithreadedTime = runMultithreaded(config);
|
||||
|
||||
System.out.println("\n=============================================");
|
||||
System.out.println("BENCHMARK COMPARISON REPORT");
|
||||
System.out.println("=============================================");
|
||||
System.out.printf("Sequential Execution Time : %d ms%n", sequentialTime);
|
||||
System.out.printf("Multithreaded Execution Time: %d ms%n", multithreadedTime);
|
||||
|
||||
double speedup = (double) sequentialTime / multithreadedTime;
|
||||
System.out.printf("Multithreaded Mode is %.2fx faster!%n", speedup);
|
||||
System.out.println("=============================================");
|
||||
}
|
||||
default -> System.out.println("Invalid choice. Exiting simulation.");
|
||||
}
|
||||
}
|
||||
|
||||
//download as multi-thread
|
||||
private static long runMultithreaded(DownloadConfig config) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 2. Create chunks
|
||||
List<ChunkStatus> chunks = ChunkUtils.createChunks(
|
||||
config.getTotalSizeMB(),
|
||||
config.getChunkCount()
|
||||
);
|
||||
List<ChunkStatus> chunks = ChunkUtils.createChunks(config.getTotalSizeMB(), config.getChunkCount());
|
||||
|
||||
// 3. Create worker threads
|
||||
List<Thread> workerThreads = new ArrayList<>();
|
||||
|
||||
for (ChunkStatus chunk : chunks) {
|
||||
DownloadWorker worker = new DownloadWorker(chunk, config);
|
||||
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.
|
||||
}
|
||||
|
||||
// 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();
|
||||
// }
|
||||
// 6. Wait for workers and monitor to finish
|
||||
try {
|
||||
for (Thread thread : workerThreads) {
|
||||
thread.join();
|
||||
}
|
||||
monitorThread.join();
|
||||
System.out.println();
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Multithreaded mode interrupted.");
|
||||
}
|
||||
|
||||
// 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.
|
||||
long endTime = System.currentTimeMillis();
|
||||
printFinalReport(chunks, config.getTotalSizeMB());
|
||||
return (endTime - startTime);
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// this final report may show 0 progress because no worker has actually run yet.
|
||||
// Until students complete the thread start/join TODOs above,
|
||||
//Running download simulation sequentially
|
||||
private static long runSequential(DownloadConfig config) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 7. Print final report
|
||||
System.out.println();
|
||||
// 2. Create chunks
|
||||
List<ChunkStatus> chunks = ChunkUtils.createChunks(config.getTotalSizeMB(), config.getChunkCount());
|
||||
|
||||
// 4. Create and start monitor thread
|
||||
ProgressMonitor monitor = new ProgressMonitor(config, chunks);
|
||||
Thread monitorThread = new Thread(monitor, "Progress-Monitor");
|
||||
monitorThread.start();
|
||||
|
||||
// 5. Execute workers sequentially on the main thread
|
||||
for (ChunkStatus chunk : chunks) {
|
||||
DownloadWorker worker = new DownloadWorker(chunk, config);
|
||||
worker.run();
|
||||
}
|
||||
|
||||
// 6. Wait for monitor to finish logging
|
||||
try {
|
||||
monitorThread.join();
|
||||
System.out.println();
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Sequential mode interrupted.");
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
printFinalReport(chunks, config.getTotalSizeMB());
|
||||
return (endTime - startTime);
|
||||
}
|
||||
|
||||
private static void printFinalReport(List<ChunkStatus> chunks, int totalSizeMB) {
|
||||
System.out.println("=== Final Report ===");
|
||||
|
||||
int completedChunks = 0;
|
||||
double downloadedMB = 0.0;
|
||||
|
||||
@@ -91,17 +146,14 @@ public class Main {
|
||||
completedChunks++;
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"Chunk " + chunk.getChunkId()
|
||||
+ ": " + chunk.getDownloadedMB()
|
||||
+ "/" + chunk.getChunkSizeMB()
|
||||
+ " MB"
|
||||
);
|
||||
System.out.printf("Chunk %d: %.2f / %.2f MB (Completed: %b)%n",
|
||||
chunk.getChunkId(), chunk.getDownloadedMB(), chunk.getChunkSizeMB(), chunk.isCompleted());
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Completed chunks: " + completedChunks + "/" + chunks.size());
|
||||
System.out.println("Downloaded total: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB");
|
||||
System.out.printf("Downloaded total: %.2f / %.2f MB%n", downloadedMB, (double) totalSizeMB);
|
||||
System.out.println("Simulation finished.");
|
||||
System.out.println("-------------------------------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import java.util.List;
|
||||
|
||||
public class ProgressMonitor implements Runnable {
|
||||
|
||||
private final DownloadConfig config;
|
||||
private final long startTime;
|
||||
private final String fileName;
|
||||
private final int totalSizeMB;
|
||||
private final List<ChunkStatus> chunks;
|
||||
@@ -12,20 +13,15 @@ public class ProgressMonitor implements Runnable {
|
||||
this.totalSizeMB = config.getTotalSizeMB();
|
||||
this.chunks = chunks;
|
||||
this.monitorDelayMs = 500;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
double lastDownloadedMB = 0.0;
|
||||
long lastCheckTime = System.currentTimeMillis();
|
||||
int monitorDelayMs = config.getMinStepDelayMs();
|
||||
|
||||
while (true) {
|
||||
double totalDownloadedMB = 0.0;
|
||||
@@ -39,10 +35,42 @@ public class ProgressMonitor implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
double timePassedSeconds = (currentTime - lastCheckTime) / 1000.0;
|
||||
|
||||
double currentSpeed = 0.0;
|
||||
if (timePassedSeconds > 0) {
|
||||
currentSpeed = (totalDownloadedMB - lastDownloadedMB) / timePassedSeconds;
|
||||
if (currentSpeed < 0) currentSpeed = 0;
|
||||
}
|
||||
|
||||
double remainingMB = config.getTotalSizeMB() - totalDownloadedMB;
|
||||
String etaStr = "Calculating...";
|
||||
if (currentSpeed > 0) {
|
||||
int etaSeconds = (int) (remainingMB / currentSpeed);
|
||||
etaStr = String.format("%ds", etaSeconds);
|
||||
} else if (remainingMB <= 0) {
|
||||
etaStr = "0s";
|
||||
}
|
||||
|
||||
lastDownloadedMB = totalDownloadedMB;
|
||||
lastCheckTime = currentTime;
|
||||
|
||||
//Progress Bar
|
||||
double percent = 0.0;
|
||||
if (totalSizeMB > 0) {
|
||||
percent = (totalDownloadedMB * 100.0) / totalSizeMB;
|
||||
}
|
||||
int barLength = 30;
|
||||
int filledLength = (int) (barLength * (percent / 100.0));
|
||||
|
||||
StringBuilder progressBar = new StringBuilder("[");
|
||||
for (int i = 0; i < barLength; i++) {
|
||||
if (i < filledLength) progressBar.append("=");
|
||||
else if (i == filledLength) progressBar.append(">");
|
||||
else progressBar.append(" ");
|
||||
}
|
||||
progressBar.append("]");
|
||||
|
||||
System.out.printf(
|
||||
"Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n",
|
||||
@@ -54,9 +82,10 @@ 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.println("All chunks have completed downloading. Monitoring stopped.");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(monitorDelayMs);
|
||||
|
||||
Reference in New Issue
Block a user