Threads, Goroutines, and async/await in Java: Writing Concurrent Code Correctly

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Concurrency in Java: From Classic Threads to Goroutines and async/await

\n\n

Java 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\n

If 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\n

Why is Concurrency Important for a Modern Java Developer?

\n\n

Imagine 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\n

Modern 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\n

Level 1: Classic Threads — The Foundation

\n\n

A 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\n

Here is what a simple example of creating a thread looks like:

\n\n
public 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\n

Pros:

\n
    \n
  • Simplicity of creation — new Thread(() -> { ... }).start();
  • \n
  • Direct lifecycle management.
  • \n
\n\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
\n\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\n

Level 2: Virtual Threads (Project Loom) — Goroutines in Java

\n\n

If 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\n

Virtual threads are "lightweight" threads managed by the JVM, not the operating system. They can be created by the millions and consume minimal memory.

\n\n

Example of creating a virtual thread:

\n\n
public 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\n

Amazing, right

Blogs

Book Recommendations