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.