Week 12 Lab Tutorial (Step-by-Step)
Course: BIC1224 – Object-Oriented Programming (Java)
Topic: Multithreading — Creating Threads, Runnable, Sleep, Join, Synchronization (Basic)
Learning Outcomes (Week 12 Lab)
By the end of this lab, students will be able to:
● Explain what a thread is and why multithreading is used
● Create threads using Thread and Runnable
● Run multiple threads simultaneously
● Use sleep() and join() correctly
● Understand race conditions
● Apply synchronized to protect shared resources
● Build small multithreaded programs (clock, download simulation, counter)
Part A — Setup & Imports
Step 1: Create a New Project
1. Open your IDE.
2. Create a Java project:
○ Project Name: BIC1224_Week12_Threads
3. Create files:
○ [Link]
○ [Link]
○ [Link]
○ [Link]
Part B — Create Thread by Extending
Thread Class
Task 1: Create MyThread Extending Thread
Step 2: Create [Link]
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
[Link](getName() + " -> " + i);
}
}
}
Step 3: Run Thread in [Link]
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
[Link]("Thread-A");
MyThread t2 = new MyThread();
[Link]("Thread-B");
[Link]();
[Link]();
}
}
✅ You should see mixed output because threads run concurrently.
Part C — Create Thread Using Runnable
Interface (Preferred)
Task 2: Create MyTask Implementing Runnable
Step 4: Create [Link]
public class MyTask implements Runnable {
private String taskName;
public MyTask(String taskName) {
[Link] = taskName;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
[Link](taskName + " -> " + i);
}
}
}
Step 5: Run Runnable in [Link]
MyTask task1 = new MyTask("Task-1");
MyTask task2 = new MyTask("Task-2");
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
[Link]();
[Link]();
✅ Threads created from Runnable also run concurrently.
Part D — sleep() for Delays (Simulation)
Task 3: Add Delay Between Prints
Step 6: Update run() to sleep safely
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
[Link](taskName + " -> " + i);
try {
[Link](500);
} catch (InterruptedException e) {
[Link](taskName + " interrupted!");
}
}
}
✅ Now output prints with a delay.
Part E — join() to Control Execution Order
Task 4: Make Main Wait for a Thread to Finish
Step 7: Use join()
Thread t1 = new Thread(new MyTask("Task-1"));
Thread t2 = new Thread(new MyTask("Task-2"));
[Link]();
try {
[Link](); // wait for Task-1 to finish
} catch (InterruptedException e) {
[Link]("Join interrupted");
}
[Link]();
✅ Task-2 starts only after Task-1 completes.
Part F — Race Condition (Problem
Demonstration)
Task 5: Shared Counter Without Synchronization
Step 8: Create a shared counter in Main
class Counter {
int count = 0;
void increment() {
count++;
}
}
Step 9: Create thread task to increment many times
Counter c = new Counter();
Runnable r = () -> {
for (int i = 0; i < 100000; i++) {
[Link]();
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
[Link]();
[Link]();
try {
[Link]();
[Link]();
} catch (InterruptedException e) {}
[Link]("Final Count = " + [Link]);
✅ Expected should be 200000, but often you will get less due to race condition.
Part G — Fix with synchronized
Task 6: Synchronize increment()
Step 10: Update Counter class
class Counter {
int count = 0;
synchronized void increment() {
count++;
}
}
Run again.
✅ Final count should be correct consistently.
Part H — Real Scenario: Bank Account
Synchronization
Task 7: Create BankAccount with synchronized
withdraw/deposit
Step 11: Create [Link]
public class BankAccount {
private double balance;
public BankAccount(double balance) {
[Link] = balance;
}
public synchronized void deposit(double amount) {
balance += amount;
}
public synchronized void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
}
}
public double getBalance() {
return balance;
}
}
Step 12: Simulate two threads using same account
BankAccount acc = new BankAccount(1000);
Runnable depositor = () -> {
for (int i = 0; i < 1000; i++) [Link](1);
};
Runnable withdrawer = () -> {
for (int i = 0; i < 1000; i++) [Link](1);
};
Thread t1 = new Thread(depositor);
Thread t2 = new Thread(withdrawer);
[Link]();
[Link]();
try { [Link](); [Link](); } catch (InterruptedException e) {}
[Link]("Final Balance = " + [Link]());
✅ Balance should remain stable and consistent.
Part I — Mini App: Download Simulation
Task 8: Simulate Download Progress Using Threads
Step 13: Create a DownloadTask runnable
class DownloadTask implements Runnable {
private String fileName;
public DownloadTask(String fileName) {
[Link] = fileName;
}
public void run() {
for (int p = 0; p <= 100; p += 20) {
[Link](fileName + " : " + p + "%");
try { [Link](400); } catch (InterruptedException e) {}
}
[Link](fileName + " download complete!");
}
}
Step 14: Run multiple downloads
Thread f1 = new Thread(new DownloadTask("File1"));
Thread f2 = new Thread(new DownloadTask("File2"));
Thread f3 = new Thread(new DownloadTask("File3"));
[Link]();
[Link]();
[Link]();
✅ You will see interleaved progress updates.
Exercises (Week 12)
Exercise 1 — Multi-threaded Alphabet & Number Printer
● Thread 1 prints A–Z
● Thread 2 prints 1–26
Use sleep() to slow output.
Exercise 2 — Digital Clock Thread (Console)
Create a thread that prints current time repeatedly:
● Use new [Link]()
● Print only HH:MM:SS format (simple substring is OK)
Exercise 3 — Thread Priority Demo
Create 3 threads and set:
● MAX_PRIORITY
● NORM_PRIORITY
● MIN_PRIORITY
Observe output order (may vary).
Exercise 4 — Safe Counter (Race Condition Fix)
Create a counter incremented by 3 threads:
● 100000 increments each
Show:
● wrong result without synchronization
● correct result with synchronization
Exercise 5 (Challenge) — Mini Ticket Booking Simulation
Shared variable: availableSeats = 10
Create multiple threads representing customers booking 1 seat.
Rules:
● No booking if seats = 0
● Must use synchronized booking method
Print booking success/failure messages.