5 Commits
8 changed files with 162 additions and 57 deletions
+41
View File
@@ -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.
+10
View File
@@ -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/
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
+23 -13
View File
@@ -21,23 +21,33 @@ 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.
while (downloaded < chunkStatus.getChunkSizeMB()) { while (downloaded < chunkStatus.getChunkSizeMB()) {
// TODO: Generate a random sleep delay between min and max delay. try {
// TODO: Sleep for that delay. int delay = config.getMinStepDelayMs()
// TODO: Generate a random download amount for this step. + random.nextInt(config.getMaxStepDelayMs() - config.getMinStepDelayMs() + 1);
// TODO: Increase downloaded, but do not go beyond chunk size. Thread.sleep(delay);
// TODO: Save the updated downloaded value into chunkStatus.
// TODO: Optionally print step-by-step progress.
}
// TODO: Mark the chunk as completed. double stepDownload = config.getMinStepDownloadMB()
// TODO: Record the chunk end time in chunkStatus. + random.nextDouble() * (config.getMaxStepDownloadMB() - config.getMinStepDownloadMB());
// TODO: Print a message that this chunk has finished downloading.
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());
} }
} }
+20 -22
View File
@@ -34,36 +34,34 @@ public class Main {
workerThreads.add(workerThread); workerThreads.add(workerThread);
// TODO: System.out.println("Assigned Chunk " + chunk.getChunkId());
// Students may print helpful debug information here,
// for example which chunk is assigned to which worker thread.
} }
// 4. Create and start monitor 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");
monitorThread.start();
// 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 // 5. Start worker threads
// TODO: for (Thread thread : workerThreads)
// Start each worker thread in workerThreads. thread.start();
// Use a loop and call start() on each thread.
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: // TODO:
// After all workers finish, the monitor thread may also need to stop. // After all workers finish, the monitor thread may also need to stop.
+48 -22
View File
@@ -16,16 +16,7 @@ public class ProgressMonitor implements Runnable {
@Override @Override
public void run() { public void run() {
// TODO: long startTimeMs = System.currentTimeMillis();
// 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;
@@ -33,37 +24,72 @@ public class ProgressMonitor implements Runnable {
for (ChunkStatus chunk : chunks) { for (ChunkStatus chunk : chunks) {
totalDownloadedMB += chunk.getDownloadedMB(); totalDownloadedMB += chunk.getDownloadedMB();
if (chunk.isCompleted()) { if (chunk.isCompleted()) {
completedChunks++; completedChunks++;
} }
} }
double percent = 0.0; long elapsedMs = System.currentTimeMillis() - startTimeMs;
if (totalSizeMB > 0) { double elapsedSeconds = elapsedMs / 1000.0;
percent = (totalDownloadedMB * 100.0) / totalSizeMB;
} 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( System.out.printf(
"Progress for %s: %.1f/%.1f MB (%.2f%%), completed chunks: %d/%d%n", "\r[%s] %6.2f%% %7.1f/%d MB | %6.2f MB/s | ETA %s | chunks %d/%d",
fileName, bar,
totalDownloadedMB,
(double) totalSizeMB,
percent, percent,
totalDownloadedMB,
totalSizeMB,
speedMBps,
formatEta(etaSeconds),
completedChunks, completedChunks,
chunks.size() chunks.size()
); );
System.out.flush();
if (completedChunks == chunks.size()) {
// TODO: System.out.println();
// If all chunks are completed, print a final message and exit the loop System.out.println("All chunks completed.");
break;
}
try { try {
Thread.sleep(monitorDelayMs); Thread.sleep(monitorDelayMs);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println();
System.out.println("Progress monitor interrupted."); System.out.println("Progress monitor interrupted.");
return; 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);
}
} }