basic
This commit is contained in:
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="26" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
|
||||||
|
# `start()` vs `run()`
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The output will be similar to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Calling run()
|
||||||
|
Running in: main
|
||||||
|
Calling start()
|
||||||
|
Running in: Thread-2
|
||||||
|
```
|
||||||
|
|
||||||
|
(The exact order of the last two lines may vary slightly because `start()` creates a new thread that runs independently.)
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
When `t1.run()` is called, the `run()` method is executed like a normal method call. It does not create a new thread. Since `main()` is the thread currently executing the code, `Thread.currentThread().getName()` returns `main`.
|
||||||
|
|
||||||
|
When `t2.start()` is called, Java creates a new thread and the JVM schedules that thread to execute the `run()` method. Because the new thread was created with the name `"Thread-2"`, the output shows `Thread-2` as the executing thread.
|
||||||
|
|
||||||
|
## Difference between `start()` and `run()`
|
||||||
|
|
||||||
|
The main difference is that `start()` creates a new thread of execution, while `run()` only executes the method in the current thread.
|
||||||
|
|
||||||
|
* Calling `run()` directly does not start a new thread. The code runs sequentially in the same thread that called it.
|
||||||
|
* Calling `start()` creates a new thread and then internally calls the `run()` method on that new thread.
|
||||||
|
* `start()` allows multiple threads to run concurrently, while `run()` behaves like a normal method call.
|
||||||
|
|
||||||
|
In this example, `t1.run()` runs inside the `main` thread, but `t2.start()` runs inside a separate thread named `Thread-2`.
|
||||||
|
|
||||||
|
## 2.Daemon Threads
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The output will usually be:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Main thread ends.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes it may also print one or more lines like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Daemon thread running...
|
||||||
|
Main thread ends.
|
||||||
|
```
|
||||||
|
|
||||||
|
The exact output depends on the timing of the JVM shutting down.
|
||||||
|
|
||||||
|
## Why?
|
||||||
|
|
||||||
|
The thread is marked as a daemon thread using:
|
||||||
|
|
||||||
|
```java
|
||||||
|
thread.setDaemon(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
Daemon threads run in the background and do not prevent the JVM from exiting. When the `main` thread finishes, there are no remaining non-daemon threads, so the JVM terminates. As a result, the daemon thread may be stopped before it completes its loop of printing messages 20 times.
|
||||||
|
|
||||||
|
## What happens if `thread.setDaemon(true)` is removed?
|
||||||
|
|
||||||
|
If `setDaemon(true)` is removed, the thread becomes a normal (user) thread. The JVM will wait for this thread to finish before shutting down.
|
||||||
|
|
||||||
|
The output will look something like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Main thread ends.
|
||||||
|
Daemon thread running...
|
||||||
|
Daemon thread running...
|
||||||
|
Daemon thread running...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The daemon thread will continue running until the loop completes, even though the `main` thread has already finished.
|
||||||
|
|
||||||
|
## Real-life use cases of daemon threads
|
||||||
|
|
||||||
|
Daemon threads are useful for background tasks that should automatically stop when the main application ends. Some examples include:
|
||||||
|
|
||||||
|
* **Garbage collection:** The JVM uses background daemon threads to manage memory cleanup.
|
||||||
|
* **Background monitoring:** Applications can use daemon threads to monitor system resources, logs, or application status.
|
||||||
|
* **Auto-save features:** A text editor or IDE might use a daemon thread to periodically save temporary data.
|
||||||
|
* **Cache cleanup:** A server application might run a daemon thread to remove expired cache entries.
|
||||||
|
* **Scheduled background tasks:** Tasks like checking for updates or refreshing data can run as daemon threads.
|
||||||
|
|
||||||
|
Daemon threads are mainly used for tasks that support the main application but are not essential for the application to finish running.
|
||||||
|
|
||||||
|
# 3. A Shorter Way to Create Threads
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The output will be:
|
||||||
|
|
||||||
|
```text id="q7k4m3"
|
||||||
|
Thread is running using a ...!
|
||||||
|
```
|
||||||
|
|
||||||
|
The message is printed from the new thread created by calling `thread.start()`.
|
||||||
|
|
||||||
|
## What is the `() -> { ... }` syntax called?
|
||||||
|
|
||||||
|
The `() -> { ... }` syntax is called a **lambda expression** in Java.
|
||||||
|
|
||||||
|
A lambda expression is a shorter way to write an implementation of a functional interface. In this example, it replaces the need to create a separate class that implements `Runnable`.
|
||||||
|
|
||||||
|
The code:
|
||||||
|
|
||||||
|
```java id="9xw2aq"
|
||||||
|
() -> {
|
||||||
|
System.out.println("Thread is running using a ...!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
acts as the implementation of the `Runnable` interface's `run()` method.
|
||||||
|
|
||||||
|
## How is this different from creating a class that extends `Thread` or implements `Runnable`?
|
||||||
|
|
||||||
|
Using a lambda expression makes the code shorter and easier to read because it avoids creating an extra class.
|
||||||
|
|
||||||
|
With `implements Runnable`, we normally create a separate class:
|
||||||
|
|
||||||
|
```java id="6g5v1p"
|
||||||
|
class MyRunnable implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Thread running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With a lambda expression, the same idea can be written directly:
|
||||||
|
|
||||||
|
```java id="v4c2km"
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
System.out.println("Thread running");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Extending `Thread` means creating a new class that inherits from the `Thread` class and overrides the `run()` method. This gives more control over the thread object but is less flexible because Java only allows a class to extend one class.
|
||||||
|
|
||||||
|
Using `Runnable` or a lambda expression is generally preferred because it separates the task being performed from the thread itself and allows the code to be more reusable.
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulates downloading a single chunk of a file.
|
|
||||||
*
|
|
||||||
* <p>This class is intentionally provided as a skeleton for students.
|
|
||||||
* The main multithreading and simulation logic should be completed
|
|
||||||
* in the run() method.</p>
|
|
||||||
*/
|
|
||||||
public class DownloadWorker implements Runnable {
|
public class DownloadWorker implements Runnable {
|
||||||
|
|
||||||
private final ChunkStatus chunkStatus;
|
private final ChunkStatus chunkStatus;
|
||||||
@@ -21,23 +15,74 @@ public class DownloadWorker implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// TODO: Record the chunk start time in chunkStatus.
|
|
||||||
|
chunkStatus.setStartTimeMs(System.currentTimeMillis());
|
||||||
double downloaded = 0.0;
|
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()
|
||||||
|
+ " (" + chunkStatus.getChunkSizeMB() + " MB)");
|
||||||
|
|
||||||
while (downloaded < chunkStatus.getChunkSizeMB()) {
|
while (downloaded < chunkStatus.getChunkSizeMB()) {
|
||||||
// TODO: Generate a random sleep delay between min and max delay.
|
|
||||||
// TODO: Sleep for that delay.
|
int delay = randomBetween(config.getMinStepDelayMs(), config.getMaxStepDelayMs());
|
||||||
// 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.
|
try {
|
||||||
// TODO: Optionally print step-by-step progress.
|
Thread.sleep(delay);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println(Thread.currentThread().getName() + " was interrupted.");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double step = randomBetween(config.getMinStepDownloadMB(), config.getMaxStepDownloadMB());
|
||||||
|
|
||||||
|
|
||||||
|
downloaded += step;
|
||||||
|
if (downloaded > chunkStatus.getChunkSizeMB()) {
|
||||||
|
downloaded = chunkStatus.getChunkSizeMB();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
chunkStatus.setDownloadedMB(downloaded);
|
||||||
|
|
||||||
|
|
||||||
|
System.out.printf("%s -> Chunk #%d: %.1f/%.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.
|
chunkStatus.setCompleted(true);
|
||||||
// TODO: Print a message that this chunk has finished downloading.
|
|
||||||
|
|
||||||
|
chunkStatus.setEndTimeMs(System.currentTimeMillis());
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println(Thread.currentThread().getName()
|
||||||
|
+ " finished Chunk #" + chunkStatus.getChunkId()
|
||||||
|
+ " in " + chunkStatus.getDownloadDurationMs() + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int randomBetween(int min, int max) {
|
||||||
|
if (min >= max) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
return min + random.nextInt(max - min + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double randomBetween(double min, double max) {
|
||||||
|
if (min >= max) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
return min + random.nextDouble() * (max - min);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-34
@@ -34,50 +34,41 @@ public class Main {
|
|||||||
|
|
||||||
workerThreads.add(workerThread);
|
workerThreads.add(workerThread);
|
||||||
|
|
||||||
// TODO:
|
System.out.println("Assigned Chunk #" + chunk.getChunkId()
|
||||||
// Students may print helpful debug information here,
|
+ " (" + chunk.getChunkSizeMB() + " MB) to " + workerThread.getName());
|
||||||
// for example which chunk is assigned to which worker thread.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Create and start monitor thread
|
|
||||||
ProgressMonitor monitor = new ProgressMonitor(config, chunks);
|
ProgressMonitor monitor = new ProgressMonitor(config, chunks);
|
||||||
Thread monitorThread = new Thread(monitor, "Progress-Monitor");
|
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();
|
|
||||||
|
|
||||||
// 5. Start worker threads
|
monitorThread.start();
|
||||||
// TODO:
|
|
||||||
// Start each worker thread in workerThreads.
|
|
||||||
// Use a loop and call start() on each thread.
|
|
||||||
|
|
||||||
// 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:
|
for (Thread thread : workerThreads) {
|
||||||
// After all workers finish, the monitor thread may also need to stop.
|
thread.start();
|
||||||
// Depending on how ProgressMonitor is implemented, students may:
|
}
|
||||||
// - wait for it to finish on its own, or
|
|
||||||
// - add a stopping mechanism in ProgressMonitor later.
|
|
||||||
//
|
for (Thread thread : workerThreads) {
|
||||||
// If your monitor finishes automatically, you may join it here.
|
try {
|
||||||
|
thread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Main thread interrupted while waiting for " + thread.getName());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
monitorThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Main thread interrupted while waiting for monitor.");
|
||||||
|
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();
|
||||||
System.out.println("=== Final Report ===");
|
System.out.println("=== Final Report ===");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
public class ProgressMonitor implements Runnable {
|
public class ProgressMonitor implements Runnable {
|
||||||
|
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
@@ -16,21 +17,11 @@ public class ProgressMonitor implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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) {
|
while (true) {
|
||||||
double totalDownloadedMB = 0.0;
|
double totalDownloadedMB = 0.0;
|
||||||
int completedChunks = 0;
|
int completedChunks = 0;
|
||||||
|
|
||||||
|
|
||||||
for (ChunkStatus chunk : chunks) {
|
for (ChunkStatus chunk : chunks) {
|
||||||
totalDownloadedMB += chunk.getDownloadedMB();
|
totalDownloadedMB += chunk.getDownloadedMB();
|
||||||
|
|
||||||
@@ -44,6 +35,7 @@ public class ProgressMonitor implements Runnable {
|
|||||||
percent = (totalDownloadedMB * 100.0) / totalSizeMB;
|
percent = (totalDownloadedMB * 100.0) / totalSizeMB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
System.out.printf(
|
System.out.printf(
|
||||||
"Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n",
|
"Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n",
|
||||||
fileName,
|
fileName,
|
||||||
@@ -55,8 +47,11 @@ public class ProgressMonitor implements Runnable {
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// TODO:
|
if (completedChunks == chunks.size()) {
|
||||||
// If all chunks are completed, print a final message and exit the loop
|
System.out.println("Progress monitor: all chunks completed. Stopping monitor.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(monitorDelayMs);
|
Thread.sleep(monitorDelayMs);
|
||||||
|
|||||||
Reference in New Issue
Block a user