Concurrency in Java: From Classic Threads to Goroutines and async/await
\n\nJava is a language that has been able to work with multiple tasks simultaneously from its very birth. But the world does not stand still. Today we will talk about concurrency in Java — from the good old threads (Thread) to virtual threads (goroutines from Project Loom) and the asynchronous approach with async/await (using CompletableFuture as an example).
\n\nIf you write in Java and want your server not to "lag" under load, while keeping your code readable, this article is for you. Let's go!
\n\nWhy is Concurrency Important for a Modern Java Developer?
\n\nImagine your website processes 10,000 requests per second. If each request is executed sequentially, users will wait forever. Concurrency in Java allows you to execute many tasks "simultaneously," efficiently using processor resources.
\n\nModern applications are not just CRUD. They are microservices, data stream processing, WebSocket, real-time analytics. Without understanding Java multithreading, you risk writing code that will either crash with an OutOfMemoryError or run like a snail.
\n\nLevel 1: Classic Threads — The Foundation
\n\nA thread in Java is the lowest-level abstraction for executing code in parallel. Each thread is a separate "lightweight" process within the operating system.
\n\nHere is what a simple example of creating a thread looks like:
\n\npublic class ThreadExample {\n public static void main(String[] args) {\n Thread thread = new Thread(() -> {\n for (int i = 0; i < 5; i++) {\n System.out.println("Hello from thread: " + i);\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n }\n });\n thread.start();\n System.out.println("Main thread continues working");\n }\n}\n\nPros:
\n- \n
- Simplicity of creation — new Thread(() -> { ... }).start(); \n
- Direct lifecycle management. \n
Cons:
\n- \n
- Creating a thread is an expensive operation (overhead for stack, context switching). \n
- Limit on quantity: 10,000 threads can kill your JVM. \n
- Complexity of synchronization: deadlock, race condition. \n
The classic approach is good for small tasks, but it is not suitable for high-load applications. This is exactly where virtual threads in Java come to the rescue.
\n\nLevel 2: Virtual Threads (Project Loom) — Goroutines in Java
\n\nIf you have heard about goroutines in Go, then Java virtual threads are almost the same thing. They appeared in Java 21 (Project Loom) and completely change the game.
\n\nVirtual threads are "lightweight" threads managed by the JVM, not the operating system. They can be created by the millions and consume minimal memory.
\n\nExample of creating a virtual thread:
\n\npublic class VirtualThreadExample {\n public static void main(String[] args) throws InterruptedException {\n // Create 100,000 virtual threads\n var threads = new Thread[100_000];\n for (int i = 0; i < threads.length; i++) {\n threads[i] = Thread.startVirtualThread(() -> {\n // Simulating work with blocking I/O\n try {\n Thread.sleep(1000);\n } catch (InterruptedException e) {\n Thread.currentThread().interrupt();\n }\n });\n }\n for (Thread t : threads) {\n t.join();\n }\n System.out.println("All virtual threads have completed!");\n }\n}\n\nAmazing, right