The Results Shocked Me — I Tried Writing Java Like It’s 2025
Legacy who? I pushed Java to the future — and it pushed back.
medium.com
자바를 2025년 답게 쓰자는 글 입니다.
몇몇 공감가고, 도입예정인 부분들에 대해서만 코멘트 남기려 합니다.
✨ Switched to Virtual Threads — Everything Changed
Virtual Thread
(이하 VT)에 대한 사용이 가장 먼저 나오는데, 기본적인 개념은 있지만 사용해 본적은 없어Dawn-Cs-Study
프로젝트를 하면서 사용해볼까 합니다.
VT의 개념자체가 자바가 사용하는 OS Thread를 VT를 핸들링하는 Carrier Thread로 사용하여
한정된 스레드 갯수로 더 많은 스레드를 태우겠다는 것으로 알고있습니다.
Self Study • masiljangajji
Self Study
github.com
아래는 VT 도입 이전, Thread, Executor 관련해 공부하면서 남긴 내용들입니다.
Thead 쓸 때마다 생성해서 쓰면 안되나요?
기존에는 Thread
객체를 직접 생성해서 start()
해야 했음
Thread thread = new Thread(() -> {
System.out.println("Hello World!");
});
thread.start(); // 새로운 스레드 생성 후 실행
thread.run(); // 기존 요청을 물고온 스레드로 실행 (스레드 생성 X)
이 방법은 조금 머리아픈게.. new Thread() 할 때마다 스레드가 생성 됨start()
실행이 끝나면 OS Thread는 즉시 반납되지만, 자바 Thread 객체는 힙에남아서 공간을 점유함
또 한 번 실행이 끝난 Thread 객체는 다시 start()
못하며, 몇 개의 스레드가 만들어졌는지 예상하기 어려움.
실행자체는 큰 문제가 없을 수 있으나, 관리의 측면에서 생산성이 매우 떨어진다.
Executor
Vs Executors
Thread
실행 정책을 추상화 하여 만든 Interface
new Thread() 를 직접 다루지 않도록 하기 위해 도입됨
public interface Executor {
void execute(Runnable command);
}
스레드 생성, 스케줄링 전략은 구현체에 위임합니다.ThreadPoolExecutor
, ScheduledThreadPoolExecutor
등이 대표적인 구현체
return new ThreadPoolExecutor(
core,
max,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
new ThreadPoolExecutor.CallerRunsPolicy()
);
그런데.. ThreadPoolExecutor
는 생성자 파라미터가 너무 많아서 설정이 귀찮습니다.
그렇기 때문에 자주 쓰는 패턴을 간단히 팩토리 메서드로 제공하기 위해 Executors
유틸 클래스가 만들어집니다.
Executors.newFixedThreadPool(int nThreads); // 고정 크기 스레드풀
Executors.newCachedThreadPool(); // 필요시 스레드 생성, 유휴 시 종료
Executors.newSingleThreadExecutor(); // 1개 스레드만 사용
Executors.newScheduledThreadPool(int core); // 주기적 실행 지원
하지만.. 실무에서는 보통 세부적인 커스텀을 하기 때문에 ThreadPoolExecutor
만들어 쓰는 경우가 더 많습니다.
여기서 한발자국 더 나아가면 이제는 OS Thread 하나에 여러 VT를 태워서 쓰는 방식으로 갑니다.
더 적은 Thread로 더 많은 작업을 수행할 수 있겠죠?
🧬 Embraced Records + Sealed Classes
레코드를 사용하자! 너무나 맞는 말입니다.
보통 DTO
를 다룰때 Record
사용하거나 VO
같은 녀석들을 다룰때 사용하지 않나 싶습니다.
개인적으로는 불변성을 지켜줘야 한다!, 얘는 불변해!, 이런 의미를 내포하고 싶을때도 사용합니다.
sealed
키워드는 사용은 안해봤는데요, 이번 기회에 알아봤습니다.
이 타입이 어떤 클래스/인터페이스가 상속할 수 있는지를 명시적으로 제한합니다.
public sealed interface Event
permits LoginEvent, LogoutEvent {}
public final class LoginEvent implements Event {
// 더 확장 불가
}
public final class LogoutEvent implements Event {
// 더 확장 불가
}
이런식으로 구현이 되어지는데, sealed
의 구현체는 항상 final, sealed, non-sealed 중 하나여야 합니다.
언제 유용한가?
sealed
는 하위 타입의 집합이 닫혀있음을 의미합니다.
따라서...
public sealed interface Event permits LoginEvent, LogoutEvent {}
public final class LoginEvent implements Event {}
public final class LogoutEvent implements Event {}
LoginEvent
,LogoutEvent
외에 다른 이벤트가 나오면 안되는 경우, 컴파일 타임때 Event
를 구현하는 상황을 막을 수 있습니다.DDD
에서는 클래스들을 집합화 하고, 이를 Context
로 묶는 경향이 있기 때문에 DDD
를 도입한다면 명시적으로 나타내기 좋지 않을까? 생각도 듭니다.
또한 switch
문과 결합시키는 패턴도 유용합니다.
sealed interface Event permits LoginEvent, LogoutEvent, SignUpEvent {}
record LoginEvent(String email) implements Event {}
record LogoutEvent(Long userId) implements Event {}
record SignUpEvent(String email, String nickname) implements Event {}
public class EventHandler {
public String handle(Event e) {
return switch (e) {
case LoginEvent login -> "로그인 처리: " + login.email();
case LogoutEvent logout -> "로그아웃 처리: " + logout.userId();
case SignUpEvent signUp -> "회원가입 처리: " + signUp.email();
};
}
public static void main(String[] args) {
EventHandler handler = new EventHandler();
System.out.println(handler.handle(new LoginEvent("user@test.com")));
System.out.println(handler.handle(new LogoutEvent(42L)));
System.out.println(handler.handle(new SignUpEvent("new@test.com", "승재")));
}
}
또한 Pattern Matching for switch
문법은 JDK 17은 preview
고 21부터 정식지원입니다.
'Article' 카테고리의 다른 글
[읽은 글] 동기·비동기, IO·NIO, 그리고 Virtual Thread (1) | 2025.09.01 |
---|---|
[읽은 글] 왜 Map은 Iterable이 아닐까? (0) | 2025.08.30 |
[읽은 글] 왜 자바 Stream은 대규모 환경에 적합하지 않은가? (1) | 2025.08.29 |
[읽은 글] 메서드 시그니처에 List 대신 Collection/Iterable을 고려해야 하는 이유 (1) | 2025.08.29 |