군만두의 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