Understanding Java Threads: From Basics to Synchronization

Threads are the smallest unit of execution within a process. A process itself is a running instance of a program—when you launch QQ.exe, the static file on disk becomes a dynamic process in memory. Inside that process, threads are the individual paths of execution that share the same address space.

Visualizing Two Threads

public class DualOutputDemo {
    private static class Worker extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                sleepOneMicro();
                System.out.println("Worker");
            }
        }
    }

    public static void main(String[] args) {
        new Worker().start();
        for (int i = 0; i < 10; i++) {
            sleepOneMicro();
            System.out.println("Main");
        }
    }

    private static void sleepOneMicro() {
        try {
            TimeUnit.MICROSECONDS.sleep(1);
        } catch (InterruptedException ignored) {}
    }
}

Running the snippet above will interleave the strings Worker and Main, proving that two logical flows are executing concurrently.

Essential Thread Utilities

public class ThreadUtilsDemo {
    public static void main(String[] args) {
        // pauseDemo();
        // yieldDemo();
        joinDemo();
    }

    private static void pauseDemo() {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                nap(500);
            }
        }).start();
    }

    private static void yieldDemo() {
        Runnable task = label -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(label + i);
                if (i % 10 == 0) Thread.yield();
            }
        };
        new Thread(() -> task.accept("A")).start();
        new Thread(() -> task.accept("B")).start();
    }

    private static void joinDemo() {
        Thread first = new Thread(() -> countDown("Alpha"));
        Thread second = new Thread(() -> {
            try {
                first.join();
            } catch (InterruptedException ignored) {}
            countDown("Beta");
        });
        first.start();
        second.start();
    }

    private static void countDown(String tag) {
        for (int i = 0; i < 100; i++) {
            System.out.println(tag + "-" + i);
            nap(500);
        }
    }

    private static void nap(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

Five Ways to Launch a Thread

public class LaunchStyles {
    static class Subclass extends Thread {
        public void run() { System.out.println("Subclass"); }
    }

    static class Task implements Runnable {
        public void run() { System.out.println("Runnable"); }
    }

    static class Job implements Callable<String> {
        public String call() { System.out.println("Callable"); return "ok"; }
    }

    public static void main(String[] args) {
        new Subclass().start();                          // 1
        new Thread(new Task()).start();                  // 2
        new Thread(() -> System.out.println("Lambda")).start(); // 3
        new Thread(new FutureTask<>(new Job())).start(); // 4
        Executors.newCachedThreadPool().submit(() -> System.out.println("Pool")); // 5
    }
}

Critical Sections and synchronized

When multiple threads mutate shared state, race conditions arise. The synchronized keyword enforces mutual exclusion.

Locking on an Arbitrary Objecct

public class Counter {
    private int value = 10;
    private final Object gate = new Object();

    public void decrement() {
        synchronized (gate) {
            value--;
            System.out.println(Thread.currentThread().getName() + " -> " + value);
        }
    }
}

Locking on this or the Whole Method

public class Counter {
    private int value = 10;

    public synchronized void decrement() {
        value--;
        System.out.println(Thread.currentThread().getName() + " -> " + value);
    }
}

Static Synchronization

public class Counter {
    private static int value = 10;

    public static synchronized void decrement() {
        value--;
        System.out.println(Thread.currentThread().getName() + " -> " + value);
    }

    public static void altDecrement() {
        synchronized (Counter.class) {
            value--;
        }
    }
}

Dirty Reads

public class BankAccount {
    private volatile String owner;
    private volatile double balance;

    public synchronized void set(String owner, double amt) {
        this.owner = owner;
        sleep(2000);
        this.balance = amt;
    }

    public /*synchronized*/ double read(String owner) {
        return balance; // may see intermediate state
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

If read is not synchronized, another thread may observe an inconsistent snapshot while set is halfway through.

Reentrancy

public class ReentrantDemo {
    synchronized void outer() {
        System.out.println("outer start");
        inner();
        System.out.println("outer end");
    }

    synchronized void inner() {
        System.out.println("inner");
    }

    public static void main(String[] args) {
        new ReentrantDemo().outer();
    }
}

The same thread may acquire the monitor multiple times; each enter must be matched by an exit.

Inheritance & Reentrancy

class Parent {
    synchronized void work() {
        System.out.println("parent logic");
    }
}

class Child extends Parent {
    @Override
    synchronized void work() {
        System.out.println("child logic");
        super.work(); // still owns the same monitor
    }
}

Calling super.work() from a subclass’s synchronized method is safe because the monitor is re-entrant.

Tags: java multithreading Synchronized ReentrantLock Thread Lifecycle

Posted on Sat, 16 May 2026 05:00:09 +0000 by scheda