JAVA/Concurrency

# Java Concurrency In Practice - Chapter 1

skysoo1111 2020. 2. 3. 17:51

Introduction

 

1.1 간략한 동시성의 역사 (A (Very) Brief History of Concurrency)

 

  • 리소스 활용 (Resource utilizatio)

프로그램은 입력 또는 출력과 같은 외부 작업을 기다려야하며 대기하는 동안 유용한 작업을 수행 할 수 없다. 이 대기 시간을 사용하여 다른 프로그램을 실행하는 것이 더 효율적일 것이다. 

 

  • 공평 (Fairness)

여러 사용자 및 프로그램이 시스템 리소스에 대해 동일한 소유권을 주장 할 수 있다.

한 프로그램을 실행 한 다음 그 프로그램이 완전히 종료된 후, 다른 프로그램을 시작하는 것보다 세밀한 시간 슬라이싱을 통해 컴퓨터 자원을 공유하는 것이 좋다.

 

  • 편의 (Convenience)

단일 프로그램이 모든 작업을 수행하도록 작성하는 것보다 각 단일 작업을 수행하고 필요에 따라 서로 작업을 조정하는 여러 프로그램을 작성하는 것이 더 바람직하다.

 

※ 스레드의 특징

  • 다중 프로세서 시스템에서 하드웨어 병렬 처리를 활용하기 위한 자연스러운 분해를 제공한다.
  • 동일한 프로그램 내의 여러 스레드를 여러 CPU에서 동시에 예약할 수 있다.
  • 때때로 경량 프로세스라고하며 대부분의 최신 운영 체제는 프로세스가 아닌 스레드를 기본 예약 단위로 처리한다.
  • 명시적인 조정이 없으면 스레드는 서로에 대해 동시에 비동기적으로 실행된다.
  • 소유 프로세스의 메모리 주소 공간을 공유하므로 프로세스 내의 모든 스레드는 동일한 변수에 액세스하고 동일한 힙에서 객체를 할당하므로 프로세스 간 메커니즘보다 세밀한 데이터 공유가 가능하다.
  • 공유 데이터에 대한 액세스를 조정하기위한 명시적인 동기화없이 스레드는 다른 스레드가 사용중인 변수를 예측할 수없는 결과로 수정할 수 있다.

 

1.2 스레드의 이점 (Benefits of Threads)

1. 여러 프로세스의 활용

프로그램이 단일 스레드인 경우 프로세서는 동기 I/O 작업이 완료 될 때까지 대기하는 동안 유휴 상태를 유지한다.

멀티 스레드 프로그램에서는 첫 번째 스레드가 I/O가 완료되기를 기다리는 동안 다른 스레드가 계속 실행될 수 있으므로 Blocking I/O 동안 응용 프로그램이 계속 진행될 수 있다.

 

2. 모델링의 단순성

한 가지 유형의 작업을 순차적으로 처리하는 프로그램은 한 번에 여러 유형의 작업을 관리하는 것보다 작성하기 쉽고 오류가 적다.

스레드도 마찬가지이다. 복잡한 비동기 워크 플로는 각각 별도의 스레드에서 실행되는 여러 간단한 동기 워크 플로로 분해되어 특정 동기화 지점에서만 서로 상호 작용하도록 구현한다면 관리하기 쉽다.

 

3. 비동기 이벤트의 간단한 조작

동기 I/O를 사용하는 경우 서버 응용 프로그램을 보다 쉽게 개발할 수 있지만 효율적으로 서비스하지 못 할 수 있다. 예를 들어 단일 스레드 응용 프로그램에서 데이터가 없는 상태에서 read를 요청하면 해당 스레드는 Block 상태가 되며, 이때는 read할 데이터가 들어오기까지 응용 프로그램으로 들어오는 모든 요청은 waiting 상태를 유지한다.

 

이 문제를 피하기 위해 단일 스레드 응용 프로그램은 비동기 I/O를 사용해야하는데 이는 동기 I/O보다 훨씬 복잡하고 오류가 발생하기 쉽다. 그러나 각 요청을 처리할 수 있는 고유 스레드가 있다면 한 스레드의 Block은 다른 스레드에 영향을 미치지 않는다.

 

역사적으로 OS는 프로세스가 만들 수 있는 스레드 수를 수백 또는 그 이하로 제한해왔으며 그로 인해 각 OS는 제한된 스레드 수로 효율을 극대화하기 위해 다중화된 I/O를 위한 효율적인 기능을 개발해왔으며, JAVA는 그 기능에 접근하기 위해 NonBlocking I/O(NIO)와 같은 라이브러리 패키지를 만들었다.

 

4. 보다 빠른 사용자 인터페이스

쇼핑몰 사이트에 접속해서 옷을 구매하기 위해 결제 버튼을 눌렀다고 가정해보자. 이 때 결제 시스템이 오래 걸린다고 했을 때 쇼핑몰이 단일 스레드로 운영된다면 사용자는 결제 완료가 뜰 때까지 무한정 대기해야 한다. 그러나 다중 스레드를 사용한다면 결제 스레드는 작업하도록 두고 다른 스레드를 통해 사용자에게 쇼핑 화면을 제공할 수 있을 것이다.

 

 

1.3 스레드의 위험 (Risks of Threads)

1. 안전성 도박 (Safety Hazards)

스레드 동기화가 충분히 이뤄지지 않은 경우 여러 스레드에서 작업 순서를 예측할 수 없고 예기치 않은 결과를 초래할 수 있다.

고유한 정수값의 시퀀스를 생성해야 하는 1.1의 UnsafeSequence는 다중 스레드에서 바람직하지 않은 결과를 초래할 수 있는 방법을 보여준다.

@NotThreadSafe
public class ex1_1_UnSafeSequence {
    private int value;

    /*
     * 유일한 값을 리턴한다.
     * Returns a unique value.
     */
    private int getNext(){
        return value++;
    }
}

위 코드는 싱글 스레드에서는 올바르게 동작하지만 다중 스레드에서는 그렇지 못한다.

아래 예제에서 스레드 A,B가 거의 동시에 동기화 처리 되지 못한 value 9에 접근했다고 했을 때 그러한 현상이 발생한다.

스레드는 동일한 메모리 주소 공간을 공유하고 동시에 실행되므로, 다른 스레드가 사용중인 변수에 접근하거나 값을 수정할 수 있다. 이것은 예기치 않은 데이터 변경으로 인해 혼란을 초래할 수 있다.

멀티 스레드 프로그램의 동작을 예측할 수 있으려면 스레드가 서로 간섭하지 않도록 공유 변수에 대한 접근을 적절히 조정해야 한다. Java에서는 이러한 조정을 위한 동기화 메커니즘을 제공한다.

@ThreadSafe
public class ex1_2_SafeSequence {
    @GuardedBy("this")
    private int value;

    private synchronized int getNext(){
        return value++;
    }
}

비동기일 때 컴파일러, 하드웨어, 런타임은 레지스터의 변수 캐싱 또는 프로세서의 로컬 캐시가 다른 스레드에 상당한 자유를 얻게 되므로 일반적으로 더 나은 성능을 지원하지만 이러한 최적화가 프로그램의 안정성을 약화 시키지 않도록 스레드간에 데이터가 공유되는 위치를 명확하게 인지하고 관리해야 한다.

 

2. 생존 위험 (Liveness Hazards)

안전은 "나쁜 일이 없다"는 것을 의미하지만, 동시에 생존함으로써 "결국 좋은 일이 일어날 것"이라는 보완적인 목표와 관련이 있다. 

 

순차 프로그램에서 발생할 수있는 한 가지 형태의 생존 실패는 코드가 절대 실행되지 않는 무한 루프를 만나는 것이다. 예를 들어 스레드 A가 스레드 B가 독점적으로 보유한느 자원을 대기하고, B가 절대 자원을 해제하지 않으면 A는 영원히 대기하는 상태를 말한다.

생존 오류는 교착 상태(deadlock), 기아 문제(starvation ) 등 다양한 형태로 나타단다. 이후 하나씩 알아보겠다.

 

3. 성능 위험(Performance Hazards)

여기서 성능은 생존과 관련된 것이다. 성능 문제는 열악한 서비스 시간, 응답성, 처리량, 리소스 소비 또는 확장성을 포함하여 광범위한 문제를 가정한다.

잘 설계된 동시 응용 프로그램을 사용하면 성능 향상이 되지만 어느 정도 런타임 오버헤드를 발생 시킨다. 이러한 모든 요소로 인해 추가 성능 비용이 발생하는데, 이 때 발생하는 비용을 분석하고 줄이는 기술에 대해서도 이후 하나씩 설명할 것이다.

 

 

1.4 스레드는 모든 곳에 (Threads are Everywhere)

실제로는 거의 모든 Java 응용 프로그램이 다중 스레드이다. 동시성이 프레임 워크에 의해 애플리케이션에 도입되면, 동시성 인식을 프레임 워크 코드로 제한하는 것은 불가능하다. 왜냐하면 프레임 워크의 특성상 본질적으로 애플리케이션 상태에 액세스하는 애플리케이션 컴포넌트로 콜백을하기 때문이다.

 

마찬가지로 스레드 안전성에 대한 요구는 프레임 워크에 의해 호출 된 구성 요소로 끝나지 않으며, 해당 구성 요소가 액세스하는 프로그램 상태에 액세스하는 모든 코드 경로로 확장된다.

  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다한국어 -> 영어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다영어 -> 한국어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다한국어 -> 영어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다영어 -> 한국어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다영어 -> 한국어...
       
    • 새로운 단어 목록 생성...
  • 복사

 

  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다한국어 -> 영어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다한국어 -> 영어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다영어 -> 한국어...
       
    • 새로운 단어 목록 생성...
  • 복사
  • 단어장에 추가
     
    • 다음에 대한 단어 목록이 없습니다영어 -> 한국어...
       
    • 새로운 단어 목록 생성...
  • 복사