Home
7Thread Creation & Thread States
7

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 Thread and by implementing Runnable
  • Explain start() vs run()
  • Describe NEW, RUNNABLE, TIMED_WAITING and TERMINATED
  • Use Thread.sleep() and join()
  • Protect shared data with synchronized methods

Before you start

  • Labs 4–6: inheritance, interfaces, overriding, try-catch with InterruptedException

Plan for the session

  1. Theory recap
  2. Walk through the program, section by section, and run it
  3. In-lab tasks, shown to the instructor
  4. Report exercises at home

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. Calling start() twice throws IllegalThreadStateException.

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.
  • synchronized lets one thread at a time run the method on the same object, preventing lost updates.

Implementation design

Program design

Implementation design

Execution flow

  1. Create a shared Counter at 0
  2. Create IncrementThread (3 increments) and two DecrementRunnable threads (3 and 2 decrements)
  3. Print every thread’s state: NEW
  4. start() all three and print states: RUNNABLE
  5. Each thread updates the counter and sleeps 100 ms
  6. main joins each thread
  7. 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

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

A thread that prints 1 to 10.
Requirements
Show a hint
Give each thread a name with super(name) so the interleaved output is readable.

In-lab task 2 of 3

Task 2: Add reset() to Counter

A synchronized reset that races with the workers.
Requirements
Show a hint
Without synchronized, reset could run between another thread’s read and write of value.

In-lab task 3 of 3

Task 3: Demonstrate Thread.yield()

Compare a thread that yields with one that does not.
Requirements
Show a hint
yield() is only a hint to the scheduler; the result can differ between machines and runs.

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

A countdown that runs in its own thread and can be cancelled.
Requirements

Lab report · exercise 2 of 2

Producer-consumer with a bounded buffer

One producer and two consumers sharing a fixed-size buffer.
Requirements

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)

What is the difference between start() and run()?
Show the answer
start() creates a new thread that calls run(); calling run() directly executes it in the current thread like any method.
Why is Runnable often preferred over extending Thread?
Show the answer
Java has single inheritance; implementing Runnable leaves the class free to extend another class and separates the task from the thread.

Discussion and analysis

Viva questions (2 of 2)

Why do we call join() before printing the final counter?
Show the answer
Otherwise main could print while the workers are still updating it.
What state is a thread in while it sleeps?
Show the answer
TIMED_WAITING; after run() returns it is TERMINATED.

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.