Skillversitas

Multithreading

Java supports multithreading natively — running several threads in parallel within a single program. Every Java program has at least one thread (the main thread). Multithreading improves application performance and responsiveness, especially for I/O-bound and CPU-bound tasks.

A Thread can be created in two ways: extending Thread or implementing Runnable (the latter is recommended, because Java supports multiple interfaces). ExecutorService (Java 5+) is the modern way to manage thread pools — more efficient than creating threads manually.

Virtual Threads (Java 21+) are a revolution — lightweight threads managed by the JVM, allowing you to create millions of threads with minimal overhead. A good fit for I/O-bound workloads such as web servers and database clients. Use Thread.ofVirtual().start(runnable).

Ways to Create Threads:

Approach Example Advantages
implements Runnable new Thread(runnable) Flexible, can implement multiple interfaces
extends Thread new MyThread() Simple for basic threads
Callable<V> ExecutorService.submit() Returns a result (Future)
Virtual Thread Thread.ofVirtual() Lightweight, Java 21+

Code Example

import java.util.concurrent.*;

public class MultithreadingJava {
    public static void main(String[] args) throws Exception {
        // Way 1: Runnable
        Runnable task1 = () -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("[Thread-1] Iterasi " + i);
                try { Thread.sleep(500); } catch (InterruptedException e) {}
            }
        };

        Thread thread1 = new Thread(task1, "Worker-1");
        thread1.start();

        // Way 2: Lambda
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("[Thread-2] Iterasi " + i);
                try { Thread.sleep(300); } catch (InterruptedException e) {}
            }
        }, "Worker-2");
        thread2.start();

        // Wait for completion
        thread1.join();
        thread2.join();
        System.out.println("Both threads done.");

        // ExecutorService (modern)
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 5; i++) {
            final int task = i;
            executor.submit(() ->
                System.out.println("Task " + task + " di " + Thread.currentThread().getName())
            );
        }
        executor.shutdown();
        executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS);

        // Virtual Thread (Java 21+)
        Thread.startVirtualThread(() ->
            System.out.println("Virtual thread: " + Thread.currentThread())
        );
        Thread.sleep(100); // give it time to print
    }
}

Code Explanation

  1. Runnable: () -> { ... } is a lambda that implements the Runnable interface — it contains the code that will run on a new thread.
  2. Thread.start(): starts a new thread. Thread.sleep(ms) pauses a thread. Thread.join() waits for a thread to finish.
  3. ExecutorService: newFixedThreadPool(3) creates a pool of 3 threads. submit() adds a task to the queue. shutdown() + awaitTermination() waits for all tasks to complete.
  4. Virtual Threads: Thread.ofVirtual().start() creates a lightweight thread — you can have thousands of them without big overhead.

Everyday Example

A parallel download program for several files:

import java.util.concurrent.*;
import java.util.List;

public class DownloadParalel {
    static String download(String url) throws InterruptedException {
        System.out.println("Mendownload: " + url);
        Thread.sleep(2000); // simulate the download
        return "Done: " + url;
    }

    public static void main(String[] args) throws Exception {
        List<String> urls = List.of(
            "https://example.com/file1.zip",
            "https://example.com/file2.zip",
            "https://example.com/file3.zip",
            "https://example.com/file4.zip"
        );

        ExecutorService executor = Executors.newFixedThreadPool(4);
        List<Future<String>> futures = urls.stream()
            .map(url -> executor.submit(() -> download(url)))
            .toList();

        for (Future<String> f : futures) {
            System.out.println(f.get());
        }

        executor.shutdown();
        System.out.println("All downloads done!");
    }
}

Summary

Multithreading is an important Java feature for performance and responsiveness. Use ExecutorService rather than creating Threads manually, for better control. Virtual Threads (Java 21+) are a game changer for I/O-bound workloads — as lightweight as goroutines in Go or coroutines in Kotlin.