Lab Manual VII · CSE 202
Thread Creation & Thread States
Create threads by extending Thread and by implementing Runnable, watch them move through NEW, RUNNABLE, TIMED_WAITING and TERMINATED, and protect a shared counter with synchronized methods.
← → move between slides · F full screen · M slide list
Before we start
Why this lab
Three workers update one shared counter at the same time. Will the final value always be correct?
Only if they take turns. This lab starts one IncrementThread and two DecrementRunnable threads on a shared Counter whose methods are synchronized.
Lab 7 of 10
Lab at a glance
After this lab you can
- Explain a thread as a lightweight path of execution
- Distinguish a process from a thread
- Create threads by extending
Threadand by implementingRunnable - Explain
start()vsrun() - Describe NEW, RUNNABLE, TIMED_WAITING and TERMINATED
- Use
Thread.sleep()andjoin() - Protect shared data with
synchronizedmethods
Before you start
- Labs 4–6: inheritance, interfaces, overriding, try-catch with InterruptedException
Plan for the session
- Theory recap
- Walk through the program, section by section, and run it
- In-lab tasks, shown to the instructor
- Report exercises at home
Lab 7 of 10
Contents
Select a part to jump to it.
Theory 1 of 5
Process and thread
- A process has its own memory; threads inside it share the heap but each has its own stack.
- Shared objects are convenient — and dangerous when several threads modify them.
Theory 2 of 5
Two ways to create a thread
class MyThread extends Thread { // way 1
@Override public void run() { ... }
}
new MyThread().start();
class MyTask implements Runnable { // way 2
@Override public void run() { ... }
}
new Thread(new MyTask()).start();- Runnable is usually preferred: your class can still extend another class, and the task is separate from the thread.
Theory 3 of 5
start() vs run()
thread.start(); // creates a new thread, which calls run() thread.run(); // just a normal method call in the current thread
- Only
start()gives concurrent execution. Callingstart()twice throwsIllegalThreadStateException.
Theory 4 of 5
Java thread states
getState()returns the enum value.sleep(ms)puts a thread in TIMED_WAITING; waiting for a lock is BLOCKED.
Theory 5 of 5
sleep(), join() and synchronized
try {
Thread.sleep(100); // pause this thread
worker.join(); // wait until worker finishes
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
public synchronized void increment() { value++; }join()makes main wait so the final value is printed only after all workers finish.synchronizedlets one thread at a time run the method on the same object, preventing lost updates.
Implementation design
Program design
Implementation design
Execution flow
- Create a shared Counter at 0
- Create IncrementThread (3 increments) and two DecrementRunnable threads (3 and 2 decrements)
- Print every thread’s state: NEW
- start() all three and print states: RUNNABLE
- Each thread updates the counter and sleeps 100 ms
- main joins each thread
- Print the final value (3 increments, 5 decrements: −2) and the TERMINATED states
The message order changes between runs; synchronization keeps every individual update correct.
Code walkthrough 1 of 7 · Counter.java
synchronized methods on the shared Counter
public synchronized void increment() {
value++;
System.out.println(Thread.currentThread().getName()
+ " incremented " + name + " to " + value);
}
public synchronized void decrement() {
value--;
System.out.println(Thread.currentThread().getName()
+ " decremented " + name + " to " + value);
}
public synchronized int getValue() {
return value;
}- One thread at a time per Counter object, so no update is lost.
Code walkthrough 2 of 7 · IncrementThread.java
Way 1: extend Thread
public class IncrementThread extends Thread {
private Counter counter;
private int iterations;
public IncrementThread(String name, Counter counter, int iterations) {
super(name);
this.counter = counter;
this.iterations = iterations;
}- The class is a thread.
super(name)passes the name to Thread.
Code walkthrough 3 of 7 · IncrementThread.java
The work goes in run()
@Override
public void run() {
for (int i = 0; i < iterations; i++) {
counter.increment();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(getName() + " was interrupted.");
Thread.currentThread().interrupt();
break;
}
}
System.out.println(getName() + " finished.");
}sleep(100)pauses so other threads get a turn.- If interrupted, restore the flag and stop.
Code walkthrough 4 of 7 · DecrementRunnable.java
Way 2: implement Runnable
@Override
public void run() {
for (int i = 0; i < iterations; i++) {
counter.decrement();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
Thread.currentThread().interrupt();
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished.");
}- Same loop, but the class is only a task.
Thread.currentThread()gives the thread running it.
Code walkthrough 5 of 7 · Lab7Demo.java
Creating the threads
// 2. Creating Threads
System.out.println("\n=== Creating Threads ===");
IncrementThread incrementThread = new IncrementThread("IncrementThread-1", counter, 3);
DecrementRunnable decrementTask1 = new DecrementRunnable(counter, 3);
DecrementRunnable decrementTask2 = new DecrementRunnable(counter, 2);
Thread decrementThread1 = new Thread(decrementTask1, "DecrementThread-1");
Thread decrementThread2 = new Thread(decrementTask2, "DecrementThread-2");
System.out.println("IncrementThread-1 state: " + incrementThread.getState());
System.out.println("DecrementThread-1 state: " + decrementThread1.getState());
System.out.println("DecrementThread-2 state: " + decrementThread2.getState());- A Runnable is wrapped in a Thread.
- Before start(), every state is NEW.
Code walkthrough 6 of 7 · Lab7Demo.java
start() all three
// 3. Starting Threads
System.out.println("\n=== Starting Threads ===");
incrementThread.start();
decrementThread1.start();
decrementThread2.start();
System.out.println("All threads started.");
System.out.println("IncrementThread-1 state after start: " + incrementThread.getState());
System.out.println("DecrementThread-1 state after start: " + decrementThread1.getState());
System.out.println("DecrementThread-2 state after start: " + decrementThread2.getState());- start() makes a new thread call run(); the states become RUNNABLE.
Code walkthrough 7 of 7 · Lab7Demo.java
join(): wait for the workers
// 4. Waiting for Threads using join()
System.out.println("\n=== Waiting for Threads to Complete ===");
try {
incrementThread.join();
System.out.println("IncrementThread-1 has terminated.");
decrementThread1.join();
System.out.println("DecrementThread-1 has terminated.");
decrementThread2.join();
System.out.println("DecrementThread-2 has terminated.");
} catch (InterruptedException e) {
System.out.println("Main thread was interrupted.");
Thread.currentThread().interrupt();
}- main pauses until each thread has terminated.
Step through the code · 1 of 2
Trace: Thread objects and their states
The Thread objects and the shared Counter live on the heap; watch their state change from NEW to TERMINATED.
Step through the code · 1 of 2
Trace: Thread objects and their states (cont.)
Step through the code · 2 of 2
Trace: start() vs run()
The same run() method, called two ways. Only start() gives it a new thread and a new stack.
Step through the code · 2 of 2
Trace: start() vs run() (cont.)
For reference · 4 files
The complete program
public class Counter {
private int value;
private String name;
public Counter(String name, int initialValue) {
this.name = name;
this.value = initialValue;
}
public synchronized void increment() {
value++;
System.out.println(Thread.currentThread().getName()
+ " incremented " + name + " to " + value);
}
public synchronized void decrement() {
value--;
System.out.println(Thread.currentThread().getName()
+ " decremented " + name + " to " + value);
}
public synchronized int getValue() {
return value;
}
public void display() {
System.out.println(name + " = " + getValue());
}
}public class IncrementThread extends Thread {
private Counter counter;
private int iterations;
public IncrementThread(String name, Counter counter, int iterations) {
super(name);
this.counter = counter;
this.iterations = iterations;
}
@Override
public void run() {
for (int i = 0; i < iterations; i++) {
counter.increment();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(getName() + " was interrupted.");
Thread.currentThread().interrupt();
break;
}
}
System.out.println(getName() + " finished.");
}
}public class DecrementRunnable implements Runnable {
private Counter counter;
private int iterations;
public DecrementRunnable(Counter counter, int iterations) {
this.counter = counter;
this.iterations = iterations;
}
@Override
public void run() {
for (int i = 0; i < iterations; i++) {
counter.decrement();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
Thread.currentThread().interrupt();
break;
}
}
System.out.println(Thread.currentThread().getName() + " finished.");
}
}public class Lab7Demo {
public static void main(String[] args) {
// 1. Shared Counter Creation
System.out.println("=== Thread Creation and Lifecycle Demo ===");
Counter counter = new Counter("SharedCounter", 0);
System.out.println("Initial counter state:");
counter.display();
// 2. Creating Threads
System.out.println("\n=== Creating Threads ===");
IncrementThread incrementThread = new IncrementThread("IncrementThread-1", counter, 3);
DecrementRunnable decrementTask1 = new DecrementRunnable(counter, 3);
DecrementRunnable decrementTask2 = new DecrementRunnable(counter, 2);
Thread decrementThread1 = new Thread(decrementTask1, "DecrementThread-1");
Thread decrementThread2 = new Thread(decrementTask2, "DecrementThread-2");
System.out.println("IncrementThread-1 state: " + incrementThread.getState());
System.out.println("DecrementThread-1 state: " + decrementThread1.getState());
System.out.println("DecrementThread-2 state: " + decrementThread2.getState());
// 3. Starting Threads
System.out.println("\n=== Starting Threads ===");
incrementThread.start();
decrementThread1.start();
decrementThread2.start();
System.out.println("All threads started.");
System.out.println("IncrementThread-1 state after start: " + incrementThread.getState());
System.out.println("DecrementThread-1 state after start: " + decrementThread1.getState());
System.out.println("DecrementThread-2 state after start: " + decrementThread2.getState());
// 4. Waiting for Threads using join()
System.out.println("\n=== Waiting for Threads to Complete ===");
try {
incrementThread.join();
System.out.println("IncrementThread-1 has terminated.");
decrementThread1.join();
System.out.println("DecrementThread-1 has terminated.");
decrementThread2.join();
System.out.println("DecrementThread-2 has terminated.");
} catch (InterruptedException e) {
System.out.println("Main thread was interrupted.");
Thread.currentThread().interrupt();
}
// 5. Final State
System.out.println("\n=== Final State ===");
counter.display();
System.out.println("IncrementThread-1 final state: " + incrementThread.getState());
System.out.println("DecrementThread-1 final state: " + decrementThread1.getState());
System.out.println("DecrementThread-2 final state: " + decrementThread2.getState());
System.out.println("All threads have completed.");
System.out.println("Main thread exiting.");
}
}Copy or download each file, save it under the same name, then compile and run: javac *.java → java Lab7Demo
Implementation
Expected output
=== Thread Creation and Lifecycle Demo === Initial counter state: SharedCounter = 0 === Creating Threads === IncrementThread-1 state: NEW DecrementThread-1 state: NEW DecrementThread-2 state: NEW === Starting Threads === All threads started. IncrementThread-1 state after start: RUNNABLE DecrementThread-1 state after start: RUNNABLE DecrementThread-2 state after start: RUNNABLE === Waiting for Threads to Complete === IncrementThread-1 incremented SharedCounter to 1 DecrementThread-1 decremented SharedCounter to 0 DecrementThread-2 decremented SharedCounter to -1 IncrementThread-1 incremented SharedCounter to 0 DecrementThread-1 decremented SharedCounter to -1 DecrementThread-2 decremented SharedCounter to -2 IncrementThread-1 incremented SharedCounter to -1 DecrementThread-1 decremented SharedCounter to -2 DecrementThread-2 finished. IncrementThread-1 finished. IncrementThread-1 has terminated. DecrementThread-1 finished. DecrementThread-1 has terminated. DecrementThread-2 has terminated. === Final State === SharedCounter = -2 IncrementThread-1 final state: TERMINATED DecrementThread-1 final state: TERMINATED DecrementThread-2 final state: TERMINATED All threads have completed. Main thread exiting.
One possible run: the order of thread messages changes every time, but the final value is always 3 − (3 + 2) = −2. (The sample printed in the manual ends at −3 with one decrement too many.)
Interactive 1 of 2
Two threads, many possible outputs
Interactive 2 of 2
Race condition lab: with and without synchronized
In-lab task 1 of 3
Task 1: Implement a PrinterThread
Show a hint
super(name) so the interleaved output is readable.In-lab task 2 of 3
Task 2: Add reset() to Counter
Show a hint
In-lab task 3 of 3
Task 3: Demonstrate Thread.yield()
Show a hint
In-lab tasks
Show your work during the lab
For each task, show the instructor:
- the source code
- a successful compilation
- the running output
- a short explanation of the logic you used
Lab report · exercise 1 of 2
Countdown timer with cancellation
Lab report · exercise 2 of 2
Producer-consumer with a bounded buffer
Lab report
Writing the lab report
- Problem statement and your design (classes, fields, methods)
- Complete source code, formatted and commented
- Sample output or screenshots for normal and edge cases
- A short discussion: what each OOP concept did in your program
Academic integrity
Copying from the internet, classmates, seniors or any other unauthorized source is strictly prohibited. Full marks may be deducted if plagiarism, copied work or academic dishonesty is detected. Complete the tasks, implementation, output analysis and report yourself.
Discussion and analysis
Viva questions (1 of 2)
start() and run()?Show the answer
Show the answer
Discussion and analysis
Viva questions (2 of 2)
join() before printing the final counter?Show the answer
Show the answer
Check yourself
Quiz
Summary
Summary
- Threads share the heap but have their own stacks.
- Create them by extending Thread or implementing Runnable; always call start().
- States: NEW → RUNNABLE ⇄ TIMED_WAITING/BLOCKED/WAITING → TERMINATED.
- sleep() pauses; join() waits for another thread.
- synchronized methods keep shared data consistent.