본문 바로가기

개발기술/설계|디자인패턴

JavaScript, Node JS, Event-Driven Programming 그리고 Java, Spring, MultiThreading

- java와 javascript의 스타일 차이. 태생이다르다

- node js의 등장

- epoll과 같은 os method의 발전

- java의 발전

 

프로그래밍 스타일 비교

 Java Style: Sequential and Conditional

In Java, you call a function, wait for the result, and then use if statements to control logic:

 

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/user")
    public String getUserInfo() {
        return userService.getUserData();
    }
}
int status = service.getStatus();

if (status == 200) {
    System.out.println("Success!");
} else {
    System.out.println("Failed!");
}
       <button id="loadUserBtn">Load User</button>
<div id="userInfo"></div>

        <script>
        document.getElementById("loadUserBtn").addEventListener("click", async () => {
            try {
                const res = await fetch("http://localhost:8080/user"); // calls Java controller
                const text = await res.text();
                document.getElementById("userInfo").textContent = text;
            } catch (e) {
                console.error("Failed to load user:", e);
            }
        });
</script>

 

  • Straightforward
  • Synchronous
  • Top-down logic
  • You are in control of the execution order

JavaScript Style (especially async): Assign logic to callback

“When the result eventually comes, then run this function.”

  • Non-blocking
  • You assign logic to the result via a callback (then)
  • The engine calls your function later
  • You give up control in exchange for asynchronous flow
 
fetch('/status')
        .then(response => {
            if (response.status === 200) {
                console.log("Success!");
            } else {
                console.log("Failed!");
            }
        });

 

 

Java: Multi-threaded, blocking I/O

  • Every request can get its own thread.
  • You call a function → the thread blocks (waits) until result is returned.
  • You scale by adding more threads (limited by memory and CPU).
String result = restTemplate.getForObject(url); // blocks thread

JavaScript: Single-threaded, non-blocking with event loop

  • JS uses one thread.
  • Instead of blocking, it says:
  • “When the result is ready, call this function.”
  • The event loop constantly checks:
  • “Is anything ready to run now?”
fetch(url).then(res => res.json()).then(data => console.log(data)); // non-blocking
Type of Difference Java JavaScript
Execution Model Thread-per-task (blocking) Event loop (non-blocking)
Concurrency model Multi-threading Single-threaded + event loop
I/O handling Blocking I/O (unless using async APIs like CompletableFuture, NIO) Non-blocking I/O by default
Built for Servers, business logic UI interaction, asynchronous web apps
Paradigm Imperative by default Event-driven by default

 

 

Yes — even in Spring MVC, there is something listening for HTTP requests.
Your @Controller is a response to an event — just like a JavaScript event listener.

Spring and JavaScript aren’t so different after all — it’s just that Spring hides the event-driven parts, while JavaScript exposes them.

 

 

1. JavaScript was born for the browser

  • JS was designed in 1995 for interactivity on web pages
  • In the browser, you can't block the UI — otherwise the page freezes
  • So it had to be asynchronous by design
button.addEventListener("click", () => {
    // react to user input
});

 

2. Then came Node.js (2009)

Node.js said: "What if we use the event loop to build web servers?"

Instead of using one thread per request like Java/Spring/Tomcat, Node.js:

  • Used a single-threaded event loop
  • Delegated I/O to the OS using non-blocking APIs
  • Handled thousands of concurrent requests using few threads

 

- java는 값을 값을 받을때까지 기다려서 한다면

- javascript는 callback을 사용해서 event를 알아서 처리하도록 한다

-

 

3. Why it became popular

 Scales well with I/O-bound workloads

Imagine:

  • 10,000 users calling an API
  • Most of the time is spent waiting for DB or external APIs

🟦 Blocking model (Java/Tomcat):
10,000 users → 10,000 threads → big memory, CPU usage

🟨 Non-blocking model (Node.js/WebFlux):
10,000 users → 20 threads (just enough to listen + react) → super efficient

 

 

Node.js Structure

Node.js is a JavaScript runtime built on V8 (Google’s JavaScript engine from Chrome)
But to run JavaScript outside the browser, it needed to:

  • Handle files
  • Handle sockets
  • Handle timers
  • Handle async I/O like a real OS-level application

 

 

is it an OS thread?

The thread pool that handles I/O in Node.js (via libuv) is made up of native OS threads

 

In Java Spring: Threads are application-level and managed by you or Spring

You (or Spring) are in charge of thread creation, usage, and termination.

 

What really happens when a Java thread does I/O (like reading from a socket or file):

  • 🧵 A Java thread (which is a real OS thread) calls a blocking method
    → e.g., InputStream.read() or JDBC connection.executeQuery()
  • 💤 The thread enters a "waiting" (blocked) state in the OS
    • It’s not running or consuming CPU
    • It is sleeping, waiting for I/O to complete
  • ⚙️ The OS is monitoring the I/O resource (disk, network, etc.)
  • 🛎 When the data is ready (e.g., response from DB, file loaded), the OS interrupts the thread
    • This is usually done via a hardware interrupt, polling, or epoll/select/kqueue depending on platform
  • 🔁 The OS tells the JVM scheduler: “This thread can run again”
  • 🏃 The thread wakes up and continues executing the rest of your code

 

No, Node.js with libuv does NOT need 1000 threads for 1000 I/O tasks — unless all 1000 are blocking tasks that must be offloaded (like file or DNS I/O).

 

✅ No threads are assigned to monitor sockets
✅ The event loop + OS pollers handle that efficiently
✅ libuv threads are used only when blocking cannot be avoided

 

Only when a truly blocking task is needed (e.g. file read, DNS), libuv sends it to a libuv thread pool thread

 

 

Node.js is efficient because most I/O tasks (especially sockets/network) are handled non-blocking by the OS itself — using epoll, kqueue, or IOCP — without blocking any thread.

 

1. Non-blocking I/O (like sockets, HTTP, TCP, etc.)

These are handled via OS-level async APIs — no libuv thread is used

  • libuv registers the file descriptor with epoll (Linux), kqueue (macOS), or IOCP (Windows)
  • The event loop waits for the OS to notify when the socket is ready
  • No thread blocks
  • 1000 concurrent sockets = ✅ 1 event loop + OS monitoring

Extremely scalable


2. Blocking I/O (like fs.readFile, crypto.pbkdf2, DNS)

These are delegated to libuv’s thread pool, so each thread does block

  • libuv’s thread pool has limited size (default: 4, max: 128 via UV_THREADPOOL_SIZE)
  • If you dispatch 1000 disk reads at once:
    • ✅ 4 (or 8, or 16...) threads start working
    • 🕒 The rest are queued in a job queue
    • The event loop stays free, still serving HTTP/socket traffic

❗ So yes — for CPU- or disk-heavy workloads, Node.js is not magic

  • It trades latency for non-blocking UI/network performance

'개발기술 > 설계|디자인패턴' 카테고리의 다른 글