development

자바 멀티스레딩 가이드 - Best Practices for Multithreading in Java

여름싼타 2023. 2. 9. 07:35
반응형

멀티스레딩 은 모든 스레드가 동시에 실행되고 있다는 착각과 함께 동일한 시점에 메모리에 수많은 스레드를 가질 수 있는 운영 체제의 기능이다. 다중 스레딩은 여러 가지 이점을 제공하지만 스레드 동기화, 기아 상태, 동시성 등과 관련된 문제를 방지하려면 모범 사례를 알고 있어야 한다.

Java의 다중 스레딩에 대한 모범 사례를 살펴보자.

 

경쟁 조건(Race Conditions) 및 데드락(Deadlocks) 방지

Java 스레드로 작업할 때 기억해야 할 가장 적절한 사항은 경쟁 조건과 교착 상태를 피하는 것이다. 경쟁 조건은 여러 스레드가 동일한 시점에서 동일한 데이터에 액세스 하려고 시도할 때 발생할 수 있다.

이로 인해 예기치 않은 결과를 경험할 수 있으며 이로 인해 프로그램에 문제가 발생할 수 있다. 스레드가 서로 완료되기를 기다릴 때 교착 상태가 발생한다. 프로그램이 정지될 수 있기 때문에 이 문제를 디버깅하고 해결하는 것이 어려울 수 있다.

 

공유 리소스에 액세스 할 때 동기화 사용

스레드 동기화를 적절하게 사용하면 경합 상태가 방지되며 공유 리소스에 액세스 할 수 있는 여러 스레드로 작업할 때 가장 좋다. 여러 스레드에서 공유 리소스에 액세스할 때 변경 가능한 개체에 대해 스레드 안전 메서드 또는 동기화된 블록을 사용한다. 먼저 잠금을 획득하지 않고 공유 리소스에 액세스 하지 않는다.

 

wait() 및 notify() 사용을 피해라.

wait() 및 notify() 메서드는 스레드를 관리하는 효율적인 방법처럼 보일 수 있지만 올바르게 사용하지 않으면 교착 상태가 발생할 수 있다. 일반적으로 다른 동기화 기술을 대신 사용하는 것이 좋다.

 

스레드 풀 사용

개발자는 Java의 스레드 풀을 활용하여 프로그램의 활성 스레드 수를 제한할 수 있다. 이렇게 하면 스레드 생성 및 관리와 관련된 오버헤드가 줄어든다. 스레드 풀은 스레드 생성, 관리 및 소멸의 오버헤드를 줄이는 데 도움이 될 수 있다.

스레드 풀을 사용하면 프로그래머가 작업에 재사용할 수 있는 정해진 수의 스레드를 생성할 수 있으므로 무언가를 실행해야 할 때마다 새 스레드를 생성할 필요가 없다.

스레드 풀을 사용할 때 풀 크기를 신중하게 고려해야 한다. 불필요한 스레드 생성을 피하면서 피크 로드를 처리할 수 있도록 풀 크기를 적절하게 조정하면 도움이 된다.

 

잠금 순서 우선순위 지정

동기화된 블록 또는 메서드로 작업할 때 두 스레드가 동시에 동일한 잠금을 획득하려고 시도하여 교착 상태가 발생하지 않도록 잠금 순서를 지정하는 것이 중요하다. 잠금 순서는 교착 상태 발생 가능성을 줄이기 위해 항상 다른 스레드에서 가장 먼저 액세스 할 가능성이 가장 높은 개체를 기반으로 해야 한다.

 

휘발성 필드 사용

휘발성 필드는 Java에서 스레드를 사용할 때 좋은 생각이다. 휘발성 필드는 여러 스레드에 의해 변경될 수 있고 여러 스레드에 의해 쓰이고 읽힐 수 있다. 휘발성 필드를 사용하면 모든 스레드가 최신 값을 볼 수 있다. 이는 스레드 간에 데이터 일관성을 보장하는 데 중요하다.

Java에서 휘발성 필드는 volatile 키워드를 사용하여 선언된다. 개발자가 휘발성 필드에 쓸 때 모든 쓰기 는 즉시 다른 스레드에 표시된다. 결과적으로 다른 스레드는 항상 최신 값을 보게 된다. 마찬가지로 휘발성 필드에서 읽을 때 모든 읽기 는 모든 스레드의 가장 최근 쓰기를 반환하도록 보장된다.

이러한 보장 때문에 휘발성 필드는 종종 스레드 간 동기화의 간단한 형태로 사용된다. 예를 들어 스레드는 휘발성 필드를 플래그로 사용하여 일부 작업이 완료되었음을 나타낼 수 있다.

다른 스레드는 이 플래그를 확인하여 언제 진행해도 안전한지 알 수 있다. 휘발성 필드는 올바른 순서를 보장하지 않는다. 즉, 한 스레드가 휘발성 필드에 쓰고 다른 스레드에서 읽는 경우 읽기 및 쓰기가 발생하는 순서가 보장되지 않는다. 단 하나의 보증이 있다. 가장 최근 쓰기를 반환한다.

 

스레드 로컬 변수 사용 방지

스레드 로컬 변수는 많은 스레드와 개체가 포함된 복잡한 응용 프로그램에서 관리 및 유지 관리가 빠르게 어려워질 수 있으므로 드물게 사용해야 한다. 일반적으로 절대적으로 필요한 경우가 아니면 스레드 로컬 변수를 사용하지 않는 것이 좋다.

 

동기화 블록을 빠르게 유지

최대 성능 및 확장성을 위해 동기화 블록을 가능한 작게 유지해야 한다. 가능한 한 동기화 블록 내에서 비용이 많이 드는 작업을 호출하거나 차단할 수 있는 호출(예: I/O 호출)을 수행하지 않는다.


잠금 해제 데이터 구조 사용

Lock-free 데이터 구조는 경합을 줄이고 확장성을 높이도록 설계되었다. 효율적인 방식으로 여러 스레드에서 공유 리소스에 액세스해야 할 때 사용하는 것이 좋다.

 

실행자 사용

새 스레드를 생성하고 다중 스레드 환경에서 실행하면 주로 컨텍스트 전환으로 인해 비용이 발생한다. Java 1.5에 도입된 Java 동시성 패키지의 일부인 Java Executor Framework를 활용할 수 있다. 기본 Java 런타임 스레딩 인프라의 래퍼이다.

실행자는 스레드 풀에서 작업을 보다 쉽게 ​​관리하고 실행할 수 있게 해주는 Java 유틸리티 클래스이다. 응용 프로그램의 스레드를 수동으로 관리하는 대신 실행기를 사용하여 관리하는 것을 고려하자.

 

스레드로부터 안전한 로깅 사용

로깅은 모든 애플리케이션에서 가장 중요한 교차 절단 문제 중 하나이다. 즉, 다중 스레드 환경에서 구현하는 것은 매우 어려울 수 있다. 스레드로부터 안전한 로깅 라이브러리 또는 프레임워크를 사용하여 로그가 스레드로부터 안전한 일관된 방식으로 올바르게 작성되었는지 확인하자.

 

성능 모니터링 및 기록

응용 프로그램의 스레드 성능을 모니터링하고 발생하는 모든 문제를 기록하고 응용 프로그램의 잠재적인 병목 현상이나 문제가 큰 문제가 되기 전에 식별해야 한다.

 

스레드로부터 안전한 라이브러리 활용

일반적인 작업의 스레드 안전 구현을 제공하는 많은 타사 라이브러리 및 프레임워크가 있다. 수행해야 하는 수동 스레드 관리의 양을 줄이기 위해 가능할 때마다 이를 사용하자.

 

Java에서 멀티스레딩 시 읽기/쓰기 잠금 사용

Java에서 읽기/쓰기 잠금을 사용하면 여러 스레드가 동시에 리소스에 대한 읽기 전용 액세스 권한을 가질 수 있지만 한 번에 하나의 스레드만 쓰기 액세스 권한을 가질 수 있다. 이렇게 하면 두 개의 스레드가 동시에 리소스에 쓰지 않아 데이터 손상이 발생할 수 있다.

 

Java에서 읽기/쓰기 잠금을 사용할 때 염두에 두어야 할 몇 가지 사항이 있다.

  • 모든 쓰기 작업이 잠금 블록 내에서 수행되는지 확인하십시오. 이렇게 하면 특정 시점에 하나의 스레드만 리소스에 쓸 수 있다.
  • 가능하면 lock( ) 대신 tryLock()을 사용하여 잠금을 획득하십시오. tryLock() 메서드는 다른 스레드가 잠금을 이미 보유하고 있는 경우 false를 반환하므로 스레드 가 불필요하게 차단되는 것을 방지할 수 있다.
  • 리소스 사용을 마친 후 가능한 한 빨리 잠금을 해제하십시오. 잠금을 너무 오래 유지하면 다른 스레드가 필요한 리소스에 액세스 하지 못할 수 있다.

 

올바른 동시 수집 사용

동시 컬렉션 은 안전하고 효율적인 방식으로 동일한 데이터 구조에 액세스 하는 여러 스레드를 처리하도록 설계되었다. 예를 들어 자주 액세스하거나 수정하는 대량의 데이터를 저장해야 하는 경우 Vector 대신 ConcurrentHashMap을 사용하는 것이 좋다.

 

원자 객체 사용

Java에서 스레드로 작업할 때 원자 개체를 사용하여 데이터가 올바르게 조작되도록 하는 것이 중요하다. Atomic 개체는 스레드로부터 안전한 방식으로 데이터에 액세스 하고 업데이트할 수 있는 간단한 방법을 제공한다. Java의 일부 원자 클래스에는 AtomicInteger , AtomicLong , AtomicBoolean 및 AtomicReference가 포함된다.

반응형