This commit is contained in:
2026-07-19 06:12:05 +03:30
parent 9e5c715088
commit 1b7327b9d9
5 changed files with 239 additions and 66 deletions
+1 -1
View File
@@ -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>
+142
View File
@@ -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.
+63 -18
View File
@@ -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
View File
@@ -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 ===");
+8 -13
View File
@@ -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);