hw8 #1

Open
Razia wants to merge 1 commits from develop into main
6 changed files with 267 additions and 264 deletions
Showing only changes of commit 5fbe9de273 - Show all commits
+150
View File
@@ -0,0 +1,150 @@
# Report - Multithreading Assignment
## 1. Difference Between start() and run()
In Java, both `start()` and `run()` are related to threads, but they behave differently.
When we call the `run()` method directly, no new thread is created. The method runs like a normal method in the current thread, usually the main thread.
When we call the `start()` method, Java creates a new thread and then executes the `run()` method inside that new thread.
Example:
```java
Thread t1 = new Thread(() -> {
System.out.println("Thread 1: " + Thread.currentThread().getName());
});
Thread t2 = new Thread(() -> {
System.out.println("Thread 2: " + Thread.currentThread().getName());
});
t1.run();
t2.start();
Possible output:
text
Thread 1: main
Thread 2: Thread-0
In this output, `t1.run()` runs in the main thread, but `t2.start()` runs in a separate thread.
So, the main difference is:
- `run()` executes the code normally in the current thread.
- `start()` creates a new thread and runs the code concurrently.
## 2. Daemon Threads
A daemon thread is a background thread that does not prevent the program from exiting.
If only daemon threads are still running and all normal user threads finish, the JVM stops the program automatically.
Example:
java
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
System.out.println("Main thread finished.");
Possible output:
text
Main thread finished.
Daemon thread is running...
The daemon thread may stop immediately after the main thread finishes because the JVM does not wait for daemon threads.
If we remove this line:
java
daemonThread.setDaemon(true);
then the thread becomes a normal user thread. In that case, the program may not stop because the infinite loop keeps running.
Real-life examples of daemon threads:
- Garbage Collector in Java
- Background monitoring tasks
- Auto-save services
- Log cleanup tasks
- Background cache cleanup
Daemon threads are useful for background tasks that should not block the application from closing.
## 3. Lambda Expressions and Runnable
The syntax `() -> { ... }` is called a lambda expression.
In Java, lambda expressions can be used to write shorter code for functional interfaces such as `Runnable`.
Traditional way:
java
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is running");
}
};
Thread thread = new Thread(task);
thread.start();
Lambda way:
java
Runnable task = () -> {
System.out.println("Task is running");
};
Thread thread = new Thread(task);
thread.start();
Output:
text
Task is running
Both versions do the same thing, but the lambda version is shorter and cleaner.
The lambda expression is useful because `Runnable` has only one abstract method: `run()`.
Main differences:
- Traditional implementation needs an anonymous class.
- Lambda expression uses shorter syntax.
- Lambda expression makes the code easier to read.
- Both can be used to create and run threads.
## 4. Practical Part Explanation
In the practical part of this assignment, a simulated download manager was implemented using multithreading.
The file is divided into several chunks. Each chunk is downloaded by a separate worker thread. Each worker updates its own `ChunkStatus`.
The `ProgressMonitor` runs separately and checks the progress of all chunks. It calculates the total downloaded size and prints the progress percentage until the download reaches 100%.
The configuration values are read from `download_config.txt`, such as file size, number of chunks, delay range, download step range, and monitor delay.
This project demonstrates the basic concepts of multithreading, including:
- Creating multiple threads
- Running tasks concurrently
- Monitoring shared progress
- Waiting for threads to finish
- Simulating a real download manager
+23 -54
View File
@@ -1,77 +1,45 @@
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class ConfigReader {
public static DownloadConfig readConfig(String fileName) {
InputStream inputStream = ConfigReader.class.getClassLoader().getResourceAsStream(fileName);
if (inputStream == null) {
throw new IllegalArgumentException("Config file not found in resources: " + fileName);
}
String configFileName = null;
int totalSizeMB = 0;
public static DownloadConfig readConfig(String filePath) {
String configFileName = "default_config";
double totalSizeMB = 0;
int chunkCount = 0;
int minStepDelayMs = 0;
int maxStepDelayMs = 0;
long minStepDelayMs = 0;
long maxStepDelayMs = 0;
double minStepDownloadMB = 0;
double maxStepDownloadMB = 0;
long monitorDelayMs = 500;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
if (line.isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("=", 2);
if (parts.length != 2) {
continue;
}
if (parts.length != 2) continue;
String key = parts[0].trim();
String value = parts[1].trim();
switch (key) {
case "fileName":
configFileName = value;
break;
case "totalSizeMB":
totalSizeMB = Integer.parseInt(value);
break;
case "chunkCount":
chunkCount = Integer.parseInt(value);
break;
case "minStepDelayMs":
minStepDelayMs = Integer.parseInt(value);
break;
case "maxStepDelayMs":
maxStepDelayMs = Integer.parseInt(value);
break;
case "minStepDownloadMB":
minStepDownloadMB = Double.parseDouble(value);
break;
case "maxStepDownloadMB":
maxStepDownloadMB = Double.parseDouble(value);
break;
default:
// Ignore unknown keys to keep parsing simple
break;
case "configFileName": configFileName = value; break;
case "totalSizeMB": totalSizeMB = Double.parseDouble(value); break;
case "chunkCount": chunkCount = Integer.parseInt(value); break;
case "minStepDelayMs": minStepDelayMs = Long.parseLong(value); break;
case "maxStepDelayMs": maxStepDelayMs = Long.parseLong(value); break;
case "minStepDownloadMB": minStepDownloadMB = Double.parseDouble(value); break;
case "maxStepDownloadMB": maxStepDownloadMB = Double.parseDouble(value); break;
case "monitorDelayMs": monitorDelayMs = Long.parseLong(value); break;
}
}
} catch (IOException e) {
throw new RuntimeException("Error reading config file: " + fileName, e);
} catch (Exception e) {
System.err.println("Error reading config: " + e.getMessage());
}
// ترتیب دقیقاً مطابق با سازنده DownloadConfig
return new DownloadConfig(
configFileName,
totalSizeMB,
@@ -79,7 +47,8 @@ public class ConfigReader {
minStepDelayMs,
maxStepDelayMs,
minStepDownloadMB,
maxStepDownloadMB
maxStepDownloadMB,
monitorDelayMs
);
}
}
+21 -54
View File
@@ -1,70 +1,37 @@
/**
* Represents the configuration parameters for the simulated download manager.
* This class is immutable to ensure thread-safety when shared among worker threads.
*/
public class DownloadConfig {
private final String fileName;
private final int totalSizeMB;
private final String configFileName;
private final double totalSizeMB;
private final int chunkCount;
private final int minStepDelayMs;
private final int maxStepDelayMs;
private final long minStepDelayMs;
private final long maxStepDelayMs;
private final double minStepDownloadMB;
private final double maxStepDownloadMB;
private final long monitorDelayMs;
/**
* Constructs a new DownloadConfig with specified simulation parameters.
*/
public DownloadConfig(String fileName, int totalSizeMB, int chunkCount,
int minStepDelayMs, int maxStepDelayMs,
double minStepDownloadMB, double maxStepDownloadMB) {
this.fileName = fileName;
public DownloadConfig(String configFileName, double totalSizeMB, int chunkCount,
long minStepDelayMs, long maxStepDelayMs,
double minStepDownloadMB, double maxStepDownloadMB,
long monitorDelayMs) {
this.configFileName = configFileName;
this.totalSizeMB = totalSizeMB;
this.chunkCount = chunkCount;
this.minStepDelayMs = minStepDelayMs;
this.maxStepDelayMs = maxStepDelayMs;
this.minStepDownloadMB = minStepDownloadMB;
this.maxStepDownloadMB = maxStepDownloadMB;
this.monitorDelayMs = monitorDelayMs;
}
// Getters
public String getFileName() {
return fileName;
}
public String getConfigFileName() { return configFileName; }
public double getTotalSizeMB() { return totalSizeMB; }
public int getChunkCount() { return chunkCount; } // معادل تعداد تردها
public long getMinStepDelayMs() { return minStepDelayMs; }
public long getMaxStepDelayMs() { return maxStepDelayMs; }
public double getMinStepDownloadMB() { return minStepDownloadMB; }
public double getMaxStepDownloadMB() { return maxStepDownloadMB; }
public long getMonitorDelayMs() { return monitorDelayMs; }
public int getTotalSizeMB() {
return totalSizeMB;
}
public int getChunkCount() {
return chunkCount;
}
public int getMinStepDelayMs() {
return minStepDelayMs;
}
public int getMaxStepDelayMs() {
return maxStepDelayMs;
}
public double getMinStepDownloadMB() {
return minStepDownloadMB;
}
public double getMaxStepDownloadMB() {
return maxStepDownloadMB;
}
@Override
public String toString() {
return "DownloadConfig{" +
"fileName='" + fileName + '\'' +
", totalSizeMB=" + totalSizeMB +
", chunkCount=" + chunkCount +
", minStepDelayMs=" + minStepDelayMs +
", maxStepDelayMs=" + maxStepDelayMs +
", minStepDownloadMB=" + minStepDownloadMB +
", maxStepDownloadMB=" + maxStepDownloadMB +
'}';
public double getChunkSizeMB() {
return (chunkCount > 0) ? (totalSizeMB / chunkCount) : 0;
}
}
+21 -24
View File
@@ -1,43 +1,40 @@
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 {
private final ChunkStatus chunkStatus;
private final DownloadConfig config;
private final Random random;
private final Random random = new Random();
public DownloadWorker(ChunkStatus chunkStatus, DownloadConfig config) {
this.chunkStatus = chunkStatus;
this.config = config;
this.random = new Random();
}
@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.
try {
while (downloaded < chunkStatus.getChunkSizeMB()) {
long delay = config.getMinStepDelayMs() +
(long)(random.nextDouble() * (config.getMaxStepDelayMs() - config.getMinStepDelayMs()));
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.
Thread.sleep(delay);
double step = config.getMinStepDownloadMB() +
(random.nextDouble() * (config.getMaxStepDownloadMB() - config.getMinStepDownloadMB()));
downloaded += step;
if (downloaded > chunkStatus.getChunkSizeMB()) {
downloaded = chunkStatus.getChunkSizeMB();
}
chunkStatus.setDownloadedMB(downloaded);
}
chunkStatus.setCompleted(true);
chunkStatus.setEndTimeMs(System.currentTimeMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// TODO: Mark the chunk as completed.
// TODO: Record the chunk end time in chunkStatus.
// TODO: Print a message that this chunk has finished downloading.
}
}
+38 -82
View File
@@ -3,105 +3,61 @@ import java.util.List;
public class Main {
public static void main(String[] args) {
System.out.println("=== Simulated Download Manager ===");
DownloadConfig config = ConfigReader.readConfig("src/main/resources/download_config.txt");
// 1. Read config
DownloadConfig config;
try {
config = ConfigReader.readConfig("download_config.txt");
} catch (Exception e) {
System.out.println("Failed to read configuration: " + e.getMessage());
return;
List<ChunkStatus> chunks = new ArrayList<>();
double chunkSize = config.getTotalSizeMB() / config.getChunkCount();
for (int i = 0; i < config.getChunkCount(); i++) {
chunks.add(new ChunkStatus(i + 1, chunkSize));
}
System.out.println("File name: " + config.getFileName());
System.out.println("Total size (MB): " + config.getTotalSizeMB());
System.out.println("Chunk count: " + config.getChunkCount());
System.out.println();
ProgressMonitor monitor = new ProgressMonitor(chunks, config);
// 2. Create chunks
List<ChunkStatus> chunks = ChunkUtils.createChunks(
config.getTotalSizeMB(),
config.getChunkCount()
);
Thread monitorThread = new Thread(monitor, "Progress-Monitor");
monitorThread.setDaemon(true);
monitorThread.start();
// 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());
Thread workerThread = new Thread(
new DownloadWorker(chunk, config),
"Worker-" + chunk.getChunkId()
);
workerThreads.add(workerThread);
// TODO:
// Students may print helpful debug information here,
// for example which chunk is assigned to which worker thread.
workerThread.start();
}
// 4. Create and start monitor thread
ProgressMonitor monitor = new ProgressMonitor(config, chunks);
Thread monitorThread = new Thread(monitor, "Progress-Monitor");
try {
for (Thread thread : workerThreads) {
thread.join();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Main thread interrupted while waiting for workers: " + e.getMessage());
}
// 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
// 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:
// 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.
// 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 ===");
int completedChunks = 0;
double downloadedMB = 0.0;
double totalDownloaded = 0;
for (ChunkStatus chunk : chunks) {
downloadedMB += chunk.getDownloadedMB();
if (chunk.isCompleted()) {
completedChunks++;
}
System.out.println(
"Chunk " + chunk.getChunkId()
+ ": " + chunk.getDownloadedMB()
+ "/" + chunk.getChunkSizeMB()
+ " MB"
);
totalDownloaded += chunk.getDownloadedMB();
}
double totalSize = config.getTotalSizeMB();
double progress = (totalDownloaded / totalSize) * 100;
System.out.println();
System.out.println("Completed chunks: " + completedChunks + "/" + chunks.size());
System.out.println("Downloaded total: " + downloadedMB + "/" + config.getTotalSizeMB() + " MB");
System.out.println("Simulation finished.");
System.out.printf(
"Progress: %.2f%% (%.2f / %.2f MB)%n",
progress,
totalDownloaded,
totalSize
);
System.out.println("All workers finished.");
}
}
+14 -50
View File
@@ -1,68 +1,32 @@
import java.util.List;
public class ProgressMonitor implements Runnable {
private final String fileName;
private final int totalSizeMB;
private final List<ChunkStatus> chunks;
private final long monitorDelayMs;
private final DownloadConfig config;
private volatile boolean running = true;
public ProgressMonitor(DownloadConfig config, List<ChunkStatus> chunks) {
this.fileName = config.getFileName();
this.totalSizeMB = config.getTotalSizeMB();
public ProgressMonitor(List<ChunkStatus> chunks, DownloadConfig config) {
this.chunks = chunks;
this.monitorDelayMs = 500;
this.config = config;
}
public void stop() { this.running = false; }
@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
while (running) {
double totalDownloaded = chunks.stream().mapToDouble(ChunkStatus::getDownloadedMB).sum();
double percent = (totalDownloaded / config.getTotalSizeMB()) * 100;
System.out.printf("\rProgress: %.2f%% (%.2f / %.2f MB)",
percent, totalDownloaded, config.getTotalSizeMB());
while (true) {
double totalDownloadedMB = 0.0;
int completedChunks = 0;
for (ChunkStatus chunk : chunks) {
totalDownloadedMB += chunk.getDownloadedMB();
if (chunk.isCompleted()) {
completedChunks++;
}
}
double percent = 0.0;
if (totalSizeMB > 0) {
percent = (totalDownloadedMB * 100.0) / totalSizeMB;
}
System.out.printf(
"Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n",
fileName,
totalDownloadedMB,
(double) totalSizeMB,
percent,
completedChunks,
chunks.size()
);
// TODO:
// If all chunks are completed, print a final message and exit the loop
if (totalDownloaded >= config.getTotalSizeMB()) break;
try {
Thread.sleep(monitorDelayMs);
Thread.sleep(config.getMonitorDelayMs());
} catch (InterruptedException e) {
System.out.println("Progress monitor interrupted.");
return;
break;
}
}
}