군만두의 IT 공부 일지

[스터디4] 09. 자바 21에서 강화된 언어 및 라이브러리 본문

프로그래밍/Java

[스터디4] 09. 자바 21에서 강화된 언어 및 라이브러리

mandus 2025. 4. 2. 18:44

21장. 자바 21에서 강화된 언어 및 라이브러리

21.6 가상 스레드

  • 가상(virtual) 스레드: 처리량이 높은 동시 애플리케이션을 개발할 때 사용할 수 있는 경량(lightweight) 스레드

가상 스레드 개요

  • 지금까지는 서버 애플리케이션에서 사용자 요청을 동시에 처리(요청별 스레드)하기 위해 스레드풀링을 사용했다.
  • 풀링: 제한된 개수로 스레드를 운용하는 것

▲ https://mandusitstudy.tistory.com/362

14장에서 스레드풀링을 학습했다. 스레드풀에서 초당 200개의 요청을 동시에 처리할 때 10개의 스레드를 사용했다면, 초당 2000개의 요청을 동시에 처리하려면 스레드풀에는 100개의 스레드가 풀링되어야 한다.

  • 자바 17까지: 운영체제가 제공하는 플랫폼(platform) 스레드를 래핑했기 때문에 스레드와 플랫폼 스레드가 1:1로 매핑된다.
    • 플랫폼 스레드는 비용(CPU 또는 메모리 사용량)이 많이 들기 때문에 스레드의 수를 제한해야 한다.
  • 자바 21: 가상 스레드를 제공하는데, 가상 스레드는 플랫폼 스레드와 n:1로 매핑된다.
    • CPU를 효율적으로 이용하면서 동시 처리량을 확장할 수 있어 플랫폼 스레드의 부족 문제를 해결할 수 있다.
    • 가상 스레드는 CPU에서 계산을 수행하는 동안만 플랫폼 스레드를 사용한다.
    • 가상 스레드가 블로킹 I/O 작업(파일 입출력, 네트워킹)을 수행할 경우 일지 중지되지만, 플랫폼 스레드는 다른 가상 스레드의 작업을 처리한다.

*플랫폼 스레드: 스레드와 가상 스레드를 구분하기 위해 스레드를 플랫폼 스레드라고 부른다. (자바 API 도큐먼트)

가상 스레드풀 생성

  • 가상 스레드는 플랫폼 스레드에 비해 생성 비용이 저렴하고(하드웨어 자원을 적게 사용하고), 개수에 제한을 받지 않는다.
  • 가상 스레드는 스레드풀에서 풀링되어서는 안 되고, 작업 건수별로 새로운 가상 스레드를 생성해서 처리해야 한다.
구분 스레드풀 생성
플랫폼 스레드풀 ExecutorService platformExecutor = Executors.newFixedThreadPool(최대 개수);
가상 스레드풀 ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
int taskNum = 10000;

// 플랫폼 스레드 100개를 풀링해서 사용하는 스레드풀 생성
ExecutorService threadExecutor = Executors.newFixedThreadPool(100);
work(taskNum, task, threadExecutor);

// 가상 스레드를 사용하는 ExecutorService 생성
ExecutorService virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor();
work(taskNum, task, virtualThreadExecutor);

가상 스레드 생성

자바 21부터는 가상 스레드를 생성하기 위해 새로운 정적 메소드 2개가 추가되었다.

리턴 타입 정적 메소드
Thread Thread.startVirtualThread(Runnable task)
Thread.Builder.OfVirtual Thread.ofVirtual()
  • startVirtualThread() 메소드: 가상 스레드를 생성하고 바로 작업 내용을 실행한다. 생성된 가상 스레드 객체를 리턴한다.
Thread thread = Thread.startVirtualThread(() -> {
    // 작업 내용
});
  • start() 메소드: 가상 스레드를 생성하고 바로 작업 내용을 실행한다. 생성된 가상 스레드 객체를 리턴한다.
Thread thread = Thread.ofVirtual()
                    .start(() -> {
                    	// 작업 내용
                    });
Thread thread = Thread.ofVirtual()
                    .name("threadName")    // 스레드 이름 설정
                    .start(() -> {
                    	// 작업 내용
                    });

플랫폼 스레드 생성

스레드를 생성하면 모두 플랫폼 스레드로 생성한다.

Runnable task = ...;
Thread thread = new Thread(task);

자바 21부터는 플랫폼 스레드를 생성하는 방법으로 Thread 클래스에 ofPlatform() 정적 메소드가 추가되었다.

리턴 타입 정적 메소드
Thread.Builder.OfPlatform Thread.ofPlatform()
// 기본(플랫폼 스레드 생성 및 실행)
Thread thread = Thread.ofPlatform()
    .start(() -> {
        // 작업 내용
    });
// 스레드 이름 지정
Thread thread = Thread.ofPlatform()
    .name("threadName")
    .start(() -> {
        // 작업 내용
    });
// 데몬 스레드 설정
Thread thread = Thread.ofPlatform()
    .daemon()
    .start(() -> {
        // 작업 내용
    });

21.7 순차 컬렉션

  • 기존 컬렉션 프레임워크에서는 순서를 갖는 컬렉션들의 공통 상위 인터페이스가 없기 때문에 순서가 있는 컬렉션들이 흩어져 있다.
    • 예) Collection ⊃ List/Set, Set ⊃ SortedSet/HashSet 등
  • 자바 21은 순서가 있는 컬렉션을 묶고, 공통 API를 제공할 목적으로 순차 컬렉션(Sequenced Collection), 순차 셋(SequencedSet), 순차 맵(SequencedMap) 인터페이스를 추가하고 기존 인터페이스의 상속 관계를 수정했다.

▲ 수정된 컬렉션 프레임워크의 인터페이스 상속 관계

순차 컬렉션

  • SequencedCollection: 순서가 있는 List와 Set 컬렉션의 최상위 인터페이스
  • 아래 메소드는 순서가 있는 List와 Set 컬렉션에서 모두 사용할 수 있다.
리턴 타입 메소드 이름 설명
void addFirst(E e) 첫 번째 요소로 추가
void addLast(E e) 마지막 요소로 추가
E getFirst() 첫 번째 요소를 가져오기
E getLast() 마지막 요소를 가져오기
E removeFirst() 첫 번째 요소를 제거하기
E removeLast() 마지막 요소를 제거하기
SequencedCollection(E) reversed() 요소의 순서를 뒤바꾸기

순차 Set

  • 순차 Set 컬렉션: 순서가 있으면서 요소의 중복 저장을 허용하지 않는다.
  • SequencedCollection의 자식인 SequencedSet 인터페이스를 별도로 상속받는다.
리턴 타입 메소드 이름 설명
SequencedSet<E> reversed() 요소의 순서를 뒤바꾸기

순차 Map

  • SequencedMap: 순서가 있는 Map 컬렉션의 최상위 인터페이스
리턴 타입 메소드 이름 설명
Map.Entry<K, V> firstEntry() 첫 번째 엔트리를 가져오기
Map.Entry<K, V> lastEntry() 마지막 엔트리를 가져오기
Map.Entry<K, V> pollFirstEntry() 첫 번째 요소를 가져오고, Map에서 제거
Map.Entry<K, V> pollLastEntry() 마지막 요소를 가져오고, Map에서 제거
V putFirst(K k, V v) 첫 번째 요소로 추가하기
V putLast(K k, V v) 마지막 요소로 추가하기
SequencedMap<K, V> reversed() 엔트리의 순서를 뒤바꾸기
SequencedSet<Map.Entry<K, V>> sequencedEntrySet() 엔트리를 요소로 하는 SequencedSet 얻기
SequencedSet<K> sequencedKeySet() 키를 요소로 하는 SequencedSet 얻기
SequencedCollection<V> sequencedValues() 값을 요소로 하는 SequencedCollection 얻기

수정할 수 없는 순차 컬렉션

자바 21에서는 요소를 변경할 수 없도록 수정할 수 없는 순차 컬렉션을 만들기 위해 Collections 클래스에 다음 정적 메소드가 추가되었다.

Collections.unmodifiableSequencedCollection(sequencedCollection)
Collections.unmodifiableSequencedSet(sequencedSet)
Collections.unmodifiableSequencedMap(sequencedMap)
  • 3가지 외에 Collection에는 unmodifiableXXX() 메소드들이 정의되어 있다.
    • unmodifiableXXX() 메소드: 원본 컬렉션의 요소를 보호하고 싶을 때 이용해서 불변 복제 컬렉션을 만들고 전달한다.

21.8 기본 문자셋 변경

  • 자바는 JVM이 실행될 때 운영체제의 환경에 따라 자바 기본 문자셋이 결정되었다.
    • 맥OS: UTF-8을 기본 문자셋으로 사용했다.
    • 한글 위도우: x-windows-949(MS949)를 사용했다.
  • 자바 21부터는 자바 기본 문자셋이 UTF-8로 통일되었기 때문에 운영체제의 환경에 의존하지 않는다.

바이트 수

  • 기본 문자셋이 달라지면 숫자와 영어와는 다르게, 한글 문자는 처리 바이트 수가 달라진다.
    • MS949: 한글 문자를 2byte로 처리한다.
    • UTF-8: 한글 문자를 3byte로 처리한다.

호환성 있는 코드

자바 기본 문자셋이 다르면 바이트 수와 파일 크기가 달라진다. 바이트 수에 민감한 애플리케이션은 자바 기본 문자셋과 상관없이 호환성 있는 코드로 수정할 필요가 있다.

byte[] bytes = "자바".getBytes();		// 바이트 수가 다름
FileWriter writer = new FileWriter(file);	// 파일 크기가 다름

// 호환성 있는 코드로 수정(항상 UTF-8 문자셋 사용)
byte[] bytes = "자바".getBytes(Charset.forName("UTF-8"));
FileWriter writer = new FileWriter(file, Charset.forName("UTF-8"));

 

이 글은 『이것이 자바다』 책을 학습한 내용을 정리한 것입니다.
Comments