Skip to content

Instantly share code, notes, and snippets.

@lynas
Last active May 4, 2025 16:02
Show Gist options
  • Save lynas/123ddebf291f019779bc87894f3d9b56 to your computer and use it in GitHub Desktop.
Save lynas/123ddebf291f019779bc87894f3d9b56 to your computer and use it in GitHub Desktop.

Java Multithreading

Concurrency

public class Main {
    public static void main(String[] args) throws Exception {
        Counter counter = new Counter();
        var t1 = new CounterIncrementerThread(counter);
        var t2 = new CounterIncrementerThread(counter);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.getCount());
    }
}

class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }
    public int getCount() {
        return count;
    }
}

class CounterIncrementerThread extends Thread {
    private Counter counter;
    public CounterIncrementerThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }
}

Result of System.out.println(counter.getCount()); will not return 2000

  • Because count value is updating with multiple thread simulteneusly
  • Solution to get 2000 always
public synchronized void increment() {
        count++;
    }

This ensures count is only accessed by one thread at a time

  • When one thread is accessing count another thread waits because we used synchronized

If we want to use explicit lock then use Lock lock

class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        try{
            if (lock.tryLock(1L, TimeUnit.MILLISECONDS)) {
                count++;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
    public int getCount() {
        return count;
    }
}

Seperate read write lock can be used like following

class Counter {
    private int count = 0;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private Lock readLock = lock.readLock();
    private Lock writeLock = lock.writeLock();

    public void increment() {
        try {
            writeLock.lock();
            count++;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            writeLock.unlock();
        }
    }

    public int getCount() {
        try {
            readLock.lock();
            return count;
        } finally {
            readLock.unlock();
        }
    }
}

Inter Thread communication

  • Use wait to say other thead to wait
  • Use notify to notify one other thread who want to access shared resource
  • Use notifyAll to notify all thread that are waiting to access shared resource

Executor service

  • Instead of creating thread running it and joining it
  • A better way to run thread is using executor service
  • Benefit organized thread management less code
  • Use of thread pooling ( reuse open thread )
    public static void main(String[] args) throws Exception {
        Counter counter = new Counter();
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.submit(new CounterIncrementerThread(counter));
        executor.submit(new CounterIncrementerThread(counter));
        executor.submit(new CounterObserverThread(counter));
        executor.shutdown();
    }

Asynchronous

Asynchronous operation using java CompletableFuture

  • CompletableFuture in Java is part of the java.util.concurrent package and
  • is used to write asynchronous, non-blocking code more easily and clearly than using traditional threads or Future objects.

public class AsyncMain {
    public static void main(String[] args) throws Exception {
        var start = System.currentTimeMillis();
        Tasks tasks = new Tasks();
        // ExecutorService executor = Executors.newFixedThreadPool(); ( Better for CPU bound task)
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); // ( Better for IO bound task jdk 21 and above)

        // Create 5 async tasks
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> tasks.task1sec(), executor);
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> tasks.task2sec(), executor);
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> tasks.task3sec(), executor);

        // Combine all futures
        CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3);
        // Wait for all to complete
        allTasks.join();

        // Now get results (optional)
        List<String> results = Arrays.asList(task1, task2, task3).stream()
                .map(CompletableFuture::join)
                .toList();
        results.forEach(System.out::println);

        System.out.println("All tasks done.");

        executor.shutdown();

        System.out.println("Total execution time: " + (System.currentTimeMillis() - start));
    }
}

class Tasks {

    public String task1sec() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName());
        return "task1sec";
    }

    public String task2sec() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName());
        return "task2sec";
    }

    public String task3sec() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName());
        return "task3sec";
    }

}


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment