SyntaxStudy
Sign Up
Java Beginner 1 min read

Thread and Runnable

Java supports multithreading natively through the Thread class and the Runnable interface. You create a thread by either extending Thread and overriding run(), or by implementing Runnable and passing it to the Thread constructor — the latter is preferred because it separates the task from the threading mechanism and allows your class to extend another class. Calling start() creates a new OS thread and invokes run() asynchronously; calling run() directly just executes the method on the current thread. Thread lifecycle management includes sleep() which pauses the current thread for a specified duration, join() which waits for another thread to finish, and interrupt() which signals a thread to stop. When a thread is interrupted, the interrupted flag is set; if the thread is sleeping or waiting, an InterruptedException is thrown. Proper interrupt handling is important for writing responsive, well-behaved concurrent programs — always either propagate InterruptedException or restore the interrupt flag. Thread safety problems arise when multiple threads share mutable state. The synchronized keyword can be applied to methods or blocks to ensure only one thread executes the protected code at a time. The volatile keyword ensures that reads and writes to a variable are always done from main memory rather than a thread-local cache. For simple counters and flags, java.util.concurrent.atomic classes like AtomicInteger and AtomicBoolean provide lock-free thread-safe operations.
Example
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo {

    // Runnable task (preferred over extending Thread)
    static class CounterTask implements Runnable {
        private final String name;
        private final int    count;

        CounterTask(String name, int count) {
            this.name  = name;
            this.count = count;
        }

        @Override
        public void run() {
            for (int i = 1; i <= count; i++) {
                System.out.printf("[%s] step %d%n", name, i);
                try {
                    Thread.sleep(50); // simulate work
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // restore flag
                    System.out.println(name + " interrupted");
                    return;
                }
            }
        }
    }

    // Thread-safe counter using AtomicInteger
    static AtomicInteger sharedCounter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new CounterTask("Alpha", 3));
        Thread t2 = new Thread(new CounterTask("Beta",  3));

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

        t1.join(); // wait for t1 to finish
        t2.join(); // wait for t2 to finish

        System.out.println("Both threads finished");

        // Concurrent increments with AtomicInteger
        Runnable incrementer = () -> {
            for (int i = 0; i < 1000; i++) sharedCounter.incrementAndGet();
        };
        Thread[] workers = new Thread[5];
        for (int i = 0; i < 5; i++) { workers[i] = new Thread(incrementer); workers[i].start(); }
        for (Thread w : workers) w.join();
        System.out.println("Shared counter (expected 5000): " + sharedCounter.get());
    }
}