Week 0: Same Code, Different Output

Stage: Seeing Concurrency
Goal: Understand that multiple threads execute in an unpredictable, interleaved order, producing different outputs from the same code.

Experiments

Experiment 1: Interleaving Threads

Let's write a method such as:

public static void interleave() {
    System.out.println("interleaving");

    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5; i++) {
            System.out.println("T1 -> " + i);
        }            
    });

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5; i++) {
            System.out.println("T2 -> " + i);
        }
    });

    t1.start();
    t2.start();
}    

 
Then we run it several times, and observe that the output is not consistent.
For example, one run may produce:
 
interleaving
T1 -> 0
T1 -> 1
T1 -> 2
T1 -> 3
T1 -> 4
T2 -> 0
T2 -> 1
T2 -> 2
T2 -> 3
T2 -> 4
 
And another run may produce a different ordering (or may appear the same ):
 
interleaving
T1 -> 0
T1 -> 1
T1 -> 2
T1 -> 3
T1 -> 4
T2 -> 0
T2 -> 1
T2 -> 2
T2 -> 3
T2 -> 4
 
And another run may produce interleaving between threads:
 
interleaving
T2 -> 0
T2 -> 1
T2 -> 2
T2 -> 3
T1 -> 0
T2 -> 4
T1 -> 1
T1 -> 2
T1 -> 3
T1 -> 4

Note on thread interleaving

In some runs, the output may appear sequential, with one thread completing before the other visibly makes progress. This can happen because the work is very fast, and the scheduler may allow a thread to run without interruption.
 
To increase the chances of observing interleaving, we can introduce small delays (e.g., using sleep). This causes threads to yield the CPU, giving the scheduler more opportunities to switch between them.
 
Even a single call to sleep introduces a scheduling point by forcing the thread to pause. In the context of a loop, this creates multiple scheduling points. Each pause gives the scheduler a chance to switch threads, increasing the likelihood of interleaving.
 
When a sleeping thread wakes up, it becomes runnable again, but there is no guarantee it will be scheduled immediately. The scheduler may continue running the current thread or switch to any other runnable thread.
 
Execution order is unpredictable because thread scheduling depends on factors outside the program's control, such as timing and system conditions.
 
However, interleaving is already possible without delays — sleep only makes it easier to observe.

Conclusion

Running the same multithreaded code can produce different outputs because thread execution is non-deterministic. The scheduler may interleave threads in different ways on each run, making execution order unpredictable.

Get the Code

Week 0: Same Code, Different Output