# Perfect Guide of JAVA Coding ## 6 객체지향 프로그래밍 ### 6.1 객체지향 프로그래밍의 개념 이해 #### [6.1.1 객체](https://hackmd.io/fJqODsXKShidq3ijMyojnQ?view) #### [6.1.2 클래스](https://hackmd.io/@hGIFQ8bTTuWdHlHMBGJYVg/클래스) #### [6.1.3 추상화](https://hackmd.io/@hGIFQ8bTTuWdHlHMBGJYVg/추상화) #### [6.1.4 캡슐화](https://hackmd.io/@hGIFQ8bTTuWdHlHMBGJYVg/캡슐화) #### [6.1.5 상속](https://hackmd.io/@hGIFQ8bTTuWdHlHMBGJYVg/상속) #### [6.1.6 다형성]() #### [6.1.7 연관]() #### [6.1.8 집약]() #### [6.1.9 구성]() ---- ### 6.2 SOLID 원칙 이해 #### 6.2.1 S #### 6.2.2 O #### 6.2.3 L #### 6.2.4 I #### 6.2.5 D ---- ### 6.3 객체지향 프로그래밍, SOLID, GOF 디자인 패턴 관련 유명 질문 #### 6.3.1 메서드 오버라이딩 #### 6.3.2 메서드 오버로딩 #### 6.3.3 공변 메서드 오버라이딩 #### 6.3.4 오버라이딩/오버로딩 메서드에서의 예외를 다룰때의 주요 제한 사항 #### 6.3.5 슈퍼클래스의 오버라이드된 메서드를 서브클래스의 오버라이딩 메서드에서 어떻게 호출하는가? #### 6.3.6 메인 메서드를 오버라이드 또는 오버로드할 수 있는가? #### 6.3.7 JAVA에서 static이 아닌 메서드를 static 메서드로 오버라이드 할 수 있는가? #### 6.3.8 JAVA Interface 안에 abstract가 아닌 메서드를 포함할 수 있는가? #### 6.3.9 default mathod를 가지는 인터페이스와 추상 클래스의 주요 차이점은 무엇인가? #### 6.3.10 추상 클래스와 인터페이스의 주요 차이점은 무엇인가? #### 6.3.11 abstract 메서드가 없는 추상 클래스를 만들 수 있는가 #### 6.3.12 추상이면서 동시에 final인 클래스를 만들 수 있는가? #### 6.3.13 다형성, 오버라이딩, 오버로딩의 차이점은 무엇인가? #### 6.3.14 바인딩 작업이란 무엇인가? #### 6.3.15 정적 바인딩과 동적 바인딩의 주요 차이점은 무엇인가? #### 6.3.16 자바에서 메서드 하이딩이란 무엇인가? #### 6.3.17 자바에서 가상 메서드를 작성할 수 있는가? #### 6.3.18 추상화와 다형성의 차이점 #### 6.3.19 다형성을 구현하는 방법으로 오버로딩을 고려할 수 있는가 #### 6.3.20 데커레이터 패턴에 적합한 객체지향 프로그래밍 개념은 무엇인가? #### 6.3.21 싱글턴 패턴은 언제 사용해야 하는가? #### 6.3.22 전략 패턴과 상태 패턴의 차이점은 무엇인가? #### 6.3.23 프록시 패턴과 데커레이터 패턴의 차이점 #### 6.3.24 퍼사드 패턴과 데커레이터 패턴의 차이점 #### 6.3.25 템플릿 메서드 패턴과 전략 패턴의 주요 차이점 #### 6.3.26 빌더패턴과 팩토리 패턴의 주요 차이점 #### 6.3.27 어댑터 패턴과 브리지 패턴의 주요 차이점 --- ## 16 동시성 ### 16.1 자바 동시성(멀티스레딩)의 개요 컴퓨터는 여러 프로그램이나 애플리케이션을 동시에 실행할 수 있음. 예를 들어 음악 재생 프로그램에서 음악을 들으면서 동시에 인터넷을 탐색할 수 있음. **프로세스**란 프로그램 또는 애플리케이션의 실행 인스턴스. 예를 들어 컴퓨터에서 ppt 아이콘을 두번 클릭하면 ppt 프로그램을 실행하는 프로세스를 시작함. **스레드**란 프로세스의 실행 가능한 가장 작은 작업 단위를 나타내는 **가벼운 하위 프로세스**.자바 스레드는 오버헤드가 상대적으로 낮고 다른 스레드와 공통 메모리 공간을 공유함. 프로세스는 하나의 메인 스레드와 여러 개의 스레드로 구성될 수 있음. [Column]프로세스와 스레드의 차이점 - 프로세스와 스레드의 가장 큰 차이점은 공통 메모리 공간의 공유 여부. 스레드는 공통 메모리 공간을 공유하지만 프로세스는 공유하지 않음. 스레드는 메모리를 공유하는 만큼 오버헤드가 많이 줄어듬. [비유] 프로세스를 '회사'라고 생각해보자. 각 회사는 자신만의 자원(사무용품, 컴퓨터, 사무실 등)을 가지고 있으며, 다른 회사의 자원을 직접 사용할 수 없다. 회사 간에 자원을 공유하려면 특정한 절차를 거쳐야 한다. 이처럼 프로세스는 독립적인 메모리 공간을 가지고 있으며, 다른 프로세스의 메모리 공간에 직접 접근할 수 없다. 이제 스레드를 '회사의 직원'이라고 생각해보자. 한 회사 내의 모든 직원은 회사의 자원을 공유하며 일한다. 또한, 한 직원이 작업을 하는 동안 다른 직원도 동시에 자신의 작업을 수행할 수 있다. 이처럼 스레드는 같은 프로세스 내에서 메모리와 자원을 공유하면서 실행되며, 멀티스레딩을 통해 동시에 여러 작업을 처리할 수 있습니다. 정리하면 프로세스는 독립적이고 자원을 공유하지 않는 반면, 스레드는 동일한 프로세스 내에서 자원을 공유하며 동시에 작업을 수행한다. **동시성**은 하나의 애플리케이션에서 여러 작업을 관리하는 능력을 의미. 프로그램이나 애플리케이션은 한 번에 하나의 작업을 처리(순차 처리)하거나 동시에 여러 작업을 처리(병행 처리) 가능. 동시성과 **병렬성**은 다른 개념. 병렬성은 애플리케이션이 서로 다른 개별 작업을 동시에 처리하는 능력. 애플리케이션은 각 작업을 동시 처리하거나 병렬 처리할 수 있는 하위 작업으로 작업을 분할하여 처리할 수 있음. [tip] 동시성은 많은 일을 한 번에 관리하는 것. 병렬성은 많은 일을 동시에 실행하는 것. [비유] 동시성(Concurrency)을 '여러 사람이 하나의 책을 번갈아 가며 읽는 것'이라고 생각해자. 한 사람이 책을 읽는 동안 다른 사람은 대기하고 있어야 한다. 한 사람이 읽는 것을 멈추면, 다음 사람이 그 책을 읽기 시작한다. 이렇게 여러 작업이 번갈아 가며 실행되지만, 한 번에 한 가지 작업만 수행된다. 이것이 바로 동시성이다. 병렬성(Parallelism)은 '각기 다른 책을 여러 사람이 동시에 읽는 것'이라고 생각하면 된다. 각 사람은 자신의 책을 읽고 있으므로, 모든 작업이 동시에 진행된다. 이것이 바로 병렬성. 동시성은 여러 작업이 번갈아 가며 실행되는 것을, 병렬성은 여러 작업이 동시에 실행되는 것을 의미. [추가설명] 동시성은 애플리케이션에서 여러 작업이 동시에 실행되는 것'처럼' 보이는 능력을 의미한다. 컴퓨터가 작업을 빠르게 전환하면서 여러 작업이 거의 동시에 진행되는 것 처럼 보인다. 멀티스테링은 이러한 동시성을 구현하는 한가지 방법이다. 멀티스레딩에서 각 스레드는 독립적인 실행 경로를 가진다. 따라서 한 프로세스 내에서 여러 스레드가 동시에 실행될 수 있다. 즉, 멀티스레딩은 프로세서의 시간을 분할하여 여러 작업을 거의 동시에 진행하게 하는 동시성의 한 형태. 각 스레드는 독립적으로 실행되므로, 하나의 스레드가 대기 상태에 있을 때 다른 스레드가 실행될 수 있다. 이런 방식으로 멀티스레딩은 여러 작업이 동시에 이루어지는 것처럼 보이게 만든다. 그러나 실제로 하나의 CPU 코어에서 동시에 여러 스레드가 실행되는 것은 아니다. 대신 CPU는 빠르게 스레드 간을 전환하면서 각 스레드를 조금씩 실행함으로써 동시성을 제공한다. 이렇게 함으로써 사용자에게는 여러 작업이 동시에 실행되는 것처럼 보이게 된다. |동시성|병렬성|설명| |---|---|---| |**O**|**O**|**다중 코어 CPU에서 동시에 여러 작업 실행**| |**O**|**X**|**한번에 2개 이상의 작업을 실행. 동시에 2개의 작업을 실행하진 X**| |**X**|**O**|**다중 코어 CPU에서 어떤 작업의 여러 하위 작업을 동시에 실행**| |**X**|**X**|**모든 작업을 한 번에 하나씩 순차 실행**| [**스레드풀**] 같은 작업을 싱행하기 위해 할당된 일련의 작업 스레드. 작업을 완료한 작업 스레드는 풀로 반환됨. 일반적으로 스레드 풀은 작업 큐로 구현되며, 스레드 풀에 포함된 스레이의 크기에 맞게 크기를 조정함. 스레드 풀의 크기는 보통 최적의 성능을 내기 위해 CPU 코어 수와 같음. [비유] 스레드풀은 택시 정류장으로 비유할 수 있다. 택시 정류장에 여러 대의 택시가 대기하고 있을때, 승객이 택시를 요청하면 대기중인 택시 중 하나가 승객을 태우로 목적지로 이동을 한다. 이동이 끝나면 택시는 다시 스탠드로 돌아와 다음 승객을 기다린다. 이처럼 스레드 풀은 여러개의 스레드가 미리 생성되어 대기하고 있는 것을 의미한다. 작업이 요청되면 대기 중인 스레드 중 하나가 그 작업을 수행한다. 작업이 끝나면 그 스레드는 다시 스레드 풀로 돌아와 다음 작업을 기다리게 된다. 스레드 풀을 사용하면 미리 생성된 스레드를 재사용할 수 있으므로, 스레드를 생성하고 소멸시키는 데 드는 비용을 줄일 수 있다. 또한 스레드의 최대 개수를 제한함으로써 시스템의 과부하를 방지할 수 있다. ### 16.2 기술 인터뷰 #### 16.2.1 스레드 생명 주기 상태 Q) 몇 개의 문장으로 자바 스레드의 상태를 열거하고 설명하세요. A) 자바 스레드의 상태는 크게 다섯 가지로 나뉩니다: 1. `신규 (New)`: 스레드 객체가 생성되었지만 아직 `start()` 메서드가 호출되지 않은 상태입니다. 2. `실행 대기(Runnable)`: 스레드가 실행 중이거나 실행을 위해 준비된 상태입니다. 스레드 스케줄러의 관리하에 실제로 실행되기를 기다리는 상태입니다. 3. `대기 (Waiting)`: 스레드가 다른 스레드의 특정 작업이 끝나기를 기다리는 상태입니다. 예를 들어 `Object.wait()`, `Thread.join()`, `LockSupport.park()` 등의 메서드에 의해 이 상태가 됩니다. 4. `시간 제한 대기 (Timed Waiting)`: 스레드가 다른 스레드의 작업이 끝나기를 일정 시간 동안 기다리는 상태입니다. `Thread.sleep()`, `Object.wait() with timeout`, `Thread.join() with timeout`, `LockSupport.parkNanos()`, `LockSupport.parkUntil()` 등의 메서드에 의해 이 상태가 됩니다. 5. `종료 (Terminated)`: 스레드의 작업이 완료되거나 예외로 인해 종료된 상태입니다. 위의 각 상태는 스레드의 생명 주기를 나타내며, 스레드의 동작을 제어하고 관리하는 데 필요합니다. #### 16.2.2 교착상태 [비유] 교착 상태를 '차량 교통 정체'로 생각해보자. 네 개의 차량이 교차로에 도착했고, 모두 직진을 원한다고 가정해보면, 각 차량은 서로 반대편으로 가려고 하는데, 앞차량이 움직이지 않으면 자신도 움직일 수 없다. 이렇게 각 차량이 서로 움직이지 못하게 만들어, 결국 모든 차량이 움직이지 못하게 되는 상황이 바로 교착 상태라고 볼 수 있다. 이처럼 컴퓨터 시스템에서 교착 상태는 두 개 이상의 프로세스나 스레드가 서로가 점유하고 있는 리소스를 요구하면서 무한히 대기하는 상황을 말한다. 이런 상황에서는 운영체제가 개입하지 않는 한 어떤 프로세스도 진행되지 못하게 된다. 이런 교착 상태는 시스템의 성능을 저하시키며, 해결하지 않으면 시스템이 멈추게 될 수도 있습니다. [추가 질문] 자바에서 교착상태가 발생하는 경우는? 자바에서 교착 상태(Deadlock)는 주로 다음 네 가지 조건이 동시에 만족될 때 발생한다: 1. `상호 배제 (Mutual Exclusion)`: 한 번에 한 스레드만이 특정 리소스를 점유할 수 있다. 다른 스레드가 그 리소스를 사용하려면, 리소스를 점유하고 있는 스레드가 리소스를 해제할 때까지 기다려야 한다. 2. `점유와 대기 (Hold and Wait)`: 스레드가 이미 어떤 리소스를 점유하고 있으면서, 다른 스레드가 점유하고 있는 리소스를 추가로 요청하는 상황. 3. `비선점 (No Preemption)`: 한 스레드가 다른 스레드로부터 리소스를 강제로 빼앗을 수 없다. 리소스를 점유하고 있는 스레드가 자발적으로 그 리소스를 해제해야만 다른 스레드가 그 리소스를 사용할 수 있다. 4. `순환 대기 (Circular Wait)`: 스레드 A, B, C가 있을 때, A가 B가 점유하고 있는 리소스를, B가 C가 점유하고 있는 리소스를, C가 A가 점유하고 있는 리소스를 요청하는 상황. 이렇게 스레드 간에 순환적으로 리소스를 요청하는 상황이 발생하면 교착 상태가 된다. 이 네 가지 조건 중 어느 하나라도 만족되지 않으면 교착 상태는 발생하지 않는다. 따라서 교착 상태를 피하려면 이 중 한 가지 이상의 조건을 제거해야 한다. 예를 들어, 스레드에게 필요한 모든 리소스를 한 번에 요청하도록 설계하거나, 리소스를 점유할 수 있는 스레드의 수를 제한하는 등의 방법이 있다. #### 16.2.3 경쟁상태 [비유] 여러개의 스레드에서 실행할 수 있고, 공유 데이터와 같은 공유 리소스를 사용하는 코드 부분 또는 블록을 임계 구역(Critical Section)이라고 한다. 즉, 마트에서 쇼핑할 때 경쟁 상태는 '마트의 마지막 할인 상품' 으로 생각해보자. 이때 임계구역은 '할인 상품' 그 자체다. 한 번에 한 명의 고객만이 이 할인 상품을 장바구니에 넣을 수 있어야 한다. 즉, 한 번에 한 스레드만이 공유 데이터를 수정할 수 있는 코드 영역을 임계 구역이라고 하자. 다음과 같은 상황을 생각해보자. 두명의 고객이 마지막 한 개의 할인 상품을 동시에 장바구니에 넣으려고 한다. 이 두 고객이 물건을 장바구니에 넣는 순서에 따라 결과가 달라진다. 한 고객이 먼저 물건을 장바구니에 넣으면, 다른 고객은 물건을 얻을 수 없게 된다. 이처럼 컴퓨터 시스템에서 경쟁 상태는 두 개 이상의 스레드나 프로세스가 공유 리소스에 동시에 접근하려고 할 때 발생한다. 이때 스레드나 프로세스의 실행 순서에 따라 결과가 달라집니다. 예를 들어, 두 스레드가 동일한 메모리 위치를 동시에 읽고 쓰려고 하면 경쟁 상태가 발생한다. 이 경우 한 스레드가 먼저 메모리를 읽거나 쓰는 순서에 따라 결과가 달라질 수 있다. 이런 경쟁 상태는 예측할 수 없는 결과를 초래하며, 심각한 버그를 유발할 수 있다. 따라서 이를 방지하기 위해 동기화 기법을 사용하여 공유 리소스에 대한 접근을 제어해야 한다. 즉, 동기화(Synchronization)는 '할인 상품을 장바구니에 넣는 순서'를 관리하는 규칙이다. 예를 들어, 고객들이 줄을 서서 순서대로 할인 상품을 장바구니에 넣도록 하는 것이다. 이처럼 동기화는 여러 스레드가 공유 데이터를 안전하게 접근하도록 하는 메커니즘이다. 자바에서는 `synchronized` 키워드를 사용하여 임계 구역을 설정하고 동기화를 수행할 수 있다. 이렇게 동기화를 통해 임계 구역에 접근하는 스레드의 순서를 제어함으로써 경쟁 상태를 방지할 수 있다. 이는 여러 스레드가 공유 데이터를 안전하게 처리할 수 있게 하며, 데이터의 일관성을 유지하는 데 중요한 역할을 한다. [추가 질문] 경쟁상태를 방지하기 위한 동기화 기법에 대해서 조금 더 구체적으로 설명해주세요. 1. synchronized 메서드 또는 블록: 자바에서 가장 기본적인 동기화 방법으로, 한 번에 한 스레드만이 synchronized 메서드 또는 블록을 실행할 수 있도록 합니다. 이를 통해 공유 데이터에 대한 동시 접근을 제어할 수 있습니다. 2. volatile 키워드: volatile 키워드는 변수를 메인 메모리에 직접 읽고 쓰도록 강제하여, 여러 스레드가 동일한 변수를 동시에 읽고 쓸 때 발생하는 문제를 방지합니다. 하지만 복잡한 동작을 수행할 때는 이 키워드만으로는 동기화를 보장할 수 없습니다. 3. ReentrantLock: ReentrantLock은 synchronized 키워드보다 더 세밀한 동기화 제어를 가능하게 합니다. 이를 통해 락을 얻고 해제하는 것을 통해 임계 구역에 대한 접근을 제어할 수 있습니다. 4. Semaphore: Semaphore는 특정 개수의 스레드만이 동시에 임계 구역에 접근할 수 있도록 합니다. 이는 리소스가 한정적일 때 유용합니다. 5. Atomic 클래스: Atomic 클래스 (예: AtomicInteger, AtomicLong 등)는 원자적 연산을 지원하여, 여러 스레드가 동일한 변수를 동시에 업데이트할 때 발생하는 문제를 방지합니다. 6. CountDownLatch, CyclicBarrier, Phaser: 이들은 여러 스레드간의 동기화를 제공하는 데 유용한 도구들입니다. 이들은 특정 조건이 만족될 때까지 스레드의 진행을 일시 중지시킵니다. #### 16.2.4 재진입 가능한 록 [비유] 재진입 가능한 록은 '현관문'으로 생각할 수 있습니다. 가정에서는 주로 가장 먼저 집에 도착한 사람이 현관문을 열고 집안으로 들어갑니다. 이 사람이 집안에서 다른 방으로 이동하려고 할 때, 현관문을 다시 열어야 할까요? 당연히 그럴 필요는 없습니다. 왜냐하면 그는 이미 집안에 들어와 현관문의 키를 소유하고 있기 때문입니다. 이처럼 컴퓨터 프로그래밍에서도, 한 스레드가 이미 록을 획득한 상태에서 해당 록을 다시 요청하면, 그 스레드는 록을 다시 획득할 수 있습니다. 이것이 바로 '재진입 가능한 록'의 개념입니다. 재진입 가능한 록은 특정 스레드가 이미 점유하고 있는 록을 다시 점유할 수 있게 해주는 메커니즘입니다. 이 기능은 재귀적인 함수 호출, 또는 하나의 스레드가 여러 메서드에서 같은 리소스에 접근해야 할 때 유용합니다. 이를 통해 스레드가 자신이 이미 점유하고 있는 록 때문에 블록되는 상황을 방지할 수 있습니다. [추가질문] synchronized 블록보다 ReentrantLock을 사용하는 이유가 무엇인가요? `synchronized` 블록과 `ReentrantLock`은 모두 자바에서 동시성 제어를 위해 사용되는 도구입니다. 그러나 `ReentrantLock`은 `synchronized` 블록보다 더 세밀한 동기화 제어를 가능하게 합니다. 다음은 `ReentrantLock`을 사용하는 주요 이유들입니다: 1. `대기시간 지정 가능`: `ReentrantLock`을 사용하면, 스레드가 락을 획득할 수 있을 때까지 얼마나 오랫동안 기다릴지 지정할 수 있습니다. 반면 `synchronized` 블록에서는 스레드가 락을 획득할 수 있을 때까지 무한정 기다려야 합니다. 2. `공정성 설정 가능`: `ReentrantLock` 생성자는 '공정성' 매개변수를 받아들입니다. 이 매개변수가 true로 설정되면, 가장 오래 기다린 스레드가 락을 획득하게 됩니다. 이를 통해 스레드 스타베이션(thread starvation)을 방지할 수 있습니다. 3. `중단 가능한 락의 획득`: `ReentrantLock`은 스레드가 대기 중인 경우에도 스레드를 안전하게 중단할 수 있는 기능을 제공합니다. 4. `여러 조건 변수 사용 가능`: `ReentrantLock`은 하나 이상의 조건 변수를 사용할 수 있게 해줍니다. 이를 통해 더 복잡한 동기화 상황을 처리할 수 있습니다. 하지만, `ReentrantLock`은 `synchronized` 블록보다 사용하기 복잡하며, 락을 명시적으로 해제해야 하기 때문에 사용 시 주의가 필요합니다. 따라서 간단한 동기화 상황에서는 `synchronized` 블록이 더 적합할 수 있습니다. #### 16.2.5 Executor와 ExecutorService [비유] 두 인터페이스를 '레스토랑'에 비유해보겠습니다. Executor는 레스토랑에서의 '주방장'에 비유할 수 있습니다. 주방장은 요리사들이 요리를 만들도록 지시하는 역할을 합니다. 마찬가지로 Executor는 주어진 작업(tasks)을 스레드에 할당하는 역할을 합니다. 그런데 주방장이 요리사들에게 요리를 만들라고 지시만 하면 충분할까요? 아닙니다. 레스토랑을 운영하려면 요리사들이 어떤 요리를 언제 만들어야 하는지, 어떤 요리 재료를 어떻게 관리해야 하는지 등 여러 가지 복잡한 조정 작업이 필요합니다. 이처럼 ExecutorService는 '레스토랑 매니저'에 비유할 수 있습니다. 레스토랑 매니저는 주방장의 역할뿐 아니라, 요리사들의 작업을 조정하고 관리하는 역할도 합니다. 예를 들어, 요리사들의 작업을 언제 시작하고 중단할지 결정하고, 요리사들이 완료한 작업을 추적하고, 요리사들이 현재 어떤 작업을 수행하고 있는지 확인하는 등의 역할을 합니다. 마찬가지로 ExecutorService는 Executor의 역할뿐 아니라, 스레드 풀의 생명주기를 관리하고, 스레드에 할당된 작업을 조정하고 추적하는 역할도 합니다. 예를 들어, 스레드 풀을 언제 시작하고 중단할지 결정하고, 완료된 작업을 추적하고, 실행 중인 작업을 취소하는 등의 역할을 합니다. 이렇게 ExecutorService는 더 세밀한 스레드 제어와 관리를 가능하게 합니다. [추가 질문] "ExecutorService의 shutdown() 메서드와 shutdownNow() 메서드의 차이점은 무엇인가요?" ExecutorService의 shutdown() 메서드와 shutdownNow() 메서드는 모두 스레드 풀을 종료하는 데 사용되지만, 그 작동 방식에는 차이가 있습니다. shutdown() 메서드는 스레드 풀에게 더 이상 새로운 작업을 받지 않도록 지시하고, 현재 진행 중인 모든 작업이 완료된 후에 스레드 풀을 종료합니다. 즉, shutdown()은 기다렸다가 스레드 풀을 종료하는 방식입니다. 반면에 shutdownNow() 메서드는 스레드 풀에게 더 이상 새로운 작업을 받지 않도록 지시하고, 현재 진행 중인 작업들을 모두 중단한 후에 스레드 풀을 즉시 종료합니다. 즉, shutdownNow()는 기다리지 않고 바로 스레드 풀을 종료하는 방식입니다. 따라서, shutdown()은 현재 실행 중인 작업이 모두 완료될 때까지 기다리는 반면, shutdownNow()는 현재 실행 중인 작업이 완료되지 않을 수도 있습니다. #### 16.2.6 Runnable과 Callable [비유] 두 인터페이스를 '택배 배송'에 비유해보겠습니다. Runnable은 '택배 배달원'에 비유할 수 있습니다. 택배 배달원은 택배를 받아서 목적지까지 배송하는 역할을 합니다. 그러나 택배 배달원은 택배를 배송한 후에는 특별한 결과를 반환하지 않습니다. 택배를 받았다는 사실 자체가 그의 업무를 수행했다는 증거입니다. 마찬가지로 Runnable 인터페이스의 run() 메서드는 특정 작업을 실행하지만, 작업의 결과를 반환하지 않습니다. run() 메서드는 void 타입이므로, 작업을 수행한 후에는 별도의 결과를 반환하지 않습니다. 또한 확인된 예외를 던질 수 없고 RuntimeException만 던질 수 있습니다. 이러한 조건 때문에 Runnable은 스레드의 실행 결과를 신경 쓰지 않는 시나리오에 적합합니다. 그런데 모든 배송이 Runnable 같지는 않습니다. 때로는 택배를 받은 사람이 '수령 증명'을 반환해야 하는 경우가 있습니다. 이 '수령 증명'은 택배를 정상적으로 받았다는 증거이며, 택배 회사나 보내는 사람에게 중요한 정보를 제공합니다. 이처럼 Callable은 '수령 증명'을 반환하는 택배 배송부에 비유할 수 있습니다. Callable 인터페이스의 call() 메서드는 특정 작업을 실행하고, 그 결과를 반환합니다. 이 결과는 후속 처리를 위해 중요한 정보를 제공하거나, 작업의 성공 여부를 확인하는 데 사용될 수 있습니다. [추가질문] Future 객체가 무엇인지 설명해주세요. Future를 사용하면 어떤 이점이 있나요? `Future` 객체는 비동기 계산의 결과를 나타냅니다. 즉, 아직 완료되지 않은 작업의 결과를 참조하는 데 사용됩니다. `Future` 객체를 통해 작업의 상태를 확인하거나, 작업의 결과를 가져오거나, 작업의 완료를 기다리는 등의 작업을 수행할 수 있습니다. `Future`를 사용하면 다음과 같은 이점이 있습니다: 1. `비동기 처리`: `Future`를 사용하면 작업을 비동기적으로 수행할 수 있습니다. 즉, 메인 스레드가 작업의 완료를 기다리지 않고 다른 작업을 계속 진행할 수 있습니다. 이는 애플리케이션의 성능을 향상시키는 데 도움이 됩니다. 2. `작업의 결과 활용`: `Future`는 작업의 결과를 가져오는 `get()` 메서드를 제공합니다. 이 메서드를 통해 비동기 작업의 결과를 활용할 수 있습니다. 3. `작업의 완료 대기`: `Future`는 작업의 완료를 기다리는 `get()` 메서드를 제공합니다. 이 메서드는 작업이 완료될 때까지 블록되므로, 필요한 경우 작업의 완료를 기다릴 수 있습니다. 4. `작업의 취소`: `Future`는 작업의 취소를 요청하는 `cancel()` 메서드를 제공합니다. 이 메서드를 통해 필요한 경우 작업을 중단할 수 있습니다. 따라서 `Future`는 비동기 계산을 수행하면서도 그 결과를 효과적으로 활용하거나 관리할 수 있게 해줍니다. 이는 복잡한 동시성 제어를 보다 쉽게 수행할 수 있도록 도와줍니다. #### 16.2.7 기아 상태 [비유] 스레드의 기아 상태를 '식당에서의 자리 기다리기'에 비유해볼 수 있습니다. 식당에는 여러 테이블이 있고, 손님들이 차례로 들어와서 자리에 앉아 식사를 합니다. 하지만 식당이 매우 붐비는 경우, 일부 손님들은 자리를 기다리는 동안 다른 손님들이 계속해서 자리를 차지하게 될 수 있습니다. 예를 들어, 큰 테이블을 원하는 4인 가족이 있는데, 식당 안에는 2인용 테이블만 비어있다면 이 가족은 계속 기다려야 합니다. 그런데 이때 2인 손님이 계속 들어와서 2인용 테이블을 차지하면, 4인 가족은 계속해서 자리를 얻지 못하고 기다리게 됩니다. 이처럼, 일부 손님(스레드)이 계속해서 자원(자리)을 차지하면서 다른 손님(스레드)이 자원을 얻지 못하고 대기하는 상태를 '기아 상태'라고 합니다. 스레드의 기아 상태는 특히 우선순위가 낮은 스레드에서 발생하기 쉽습니다. 우선순위가 높은 스레드가 계속해서 자원을 차지하면, 우선순위가 낮은 스레드는 자원을 얻지 못하고 계속해서 대기하게 됩니다. 이런 상황을 방지하기 위해선 적절한 스케줄링 전략과 동기화 기법이 필요합니다. [추가질문] Semaphore 클래스에 대해서 설명해주세요. Semaphore 클래스는 동시에 접근할 수 있는 스레드의 수를 제한하는 동기화 기법입니다. 이는 여러 스레드가 공유 자원에 동시에 접근하는 것을 제어하는 데 사용되며, 이를 통해 공유 자원에 대한 동시 접근을 제한하고 동시성 문제를 방지합니다. Semaphore는 내부적으로 일정 수의 '허가'를 가지고 있습니다. 스레드가 작업을 수행하기 위해선 먼저 Semaphore로부터 허가를 얻어야 합니다. 이 때 Semaphore의 허가가 없다면, 해당 스레드는 허가를 얻을 수 있을 때까지 대기합니다. Semaphore 클래스는 주로 두 가지 메서드를 제공합니다: acquire()와 release(). 1. acquire() 메서드는 Semaphore로부터 허가를 얻는 데 사용됩니다. 허가를 얻으면 Semaphore의 허가 수가 하나 줄어듭니다. 만약 허가가 없다면, 이 메서드는 허가를 얻을 수 있을 때까지 블록됩니다. 2. release() 메서드는 Semaphore에 허가를 반환하는 데 사용됩니다. 허가를 반환하면 Semaphore의 허가 수가 하나 늘어납니다. 이처럼 Semaphore를 사용하면 여러 스레드가 공유 자원에 접근하는 것을 제어하고, 공유 자원의 동시 접근을 제한하며, 동시성 문제를 방지하는 데 도움이 됩니다. #### 16.2.8 라이브 록 [비유] 스레드의 라이브록은 두 개 이상의 스레드가 서로 상대방의 작업 완료를 기다리면서 실제로는 아무런 진행도 이루어지지 않는 상태를 말합니다. 이를 이해하는 데 도움이 될만한 비유를 드리겠습니다. 라이브록 상태를 '통로에서의 만남'에 비유해볼 수 있습니다. 좁은 통로에서 두 사람이 정면으로 만났다고 가정해보세요. A 씨는 B 씨가 통과할 수 있도록 오른쪽으로 비켜서 섰습니다. 그러나 B 씨도 마찬가지로 A 씨가 통과할 수 있도록 자신도 오른쪽으로 비켜서 섰습니다. 이 상황에서 둘 다 다시 움직여 서로가 통과할 수 있도록 비켜서려고 하지만, 결국 또 다시 같은 위치에서 만나게 됩니다. 이런 식으로 둘 다 상대방이 통과할 수 있도록 계속해서 자신의 위치를 변경하지만, 실제로는 아무도 통과하지 못하는 상황, 이것이 라이브록 상태입니다. 스레드에서 라이브록은 두 스레드가 서로 상대방의 작업이 끝나기를 기다리면서 계속해서 상태를 변경하게 되는 상황에서 발생합니다. 이런 상황을 해결하기 위해서는 프로그램의 로직을 수정하여 상호 작용하는 스레드들이 서로를 기다리지 않도록 하는 것이 필요합니다. [추가질문] 라이브록 상태를 예방하기 위한 프로그래밍 팁이 있나요? 1. **자원 순서화**: 자원을 순서에 따라 할당하면 라이브록을 피할 수 있습니다. 각 자원에 순서 번호를 부여하고, 스레드가 항상 번호 순서대로 자원을 요청하도록 하는 것입니다. 이 방식은 스레드가 순환 종속성을 형성하는 것을 막아줍니다. 2. **역할 배정**: 라이브록이 두 스레드가 서로 상대방의 작업을 기다리는 상황에서 발생하기 때문에, 이런 상황을 피하기 위해 스레드의 역할을 명확히 정의하는 것이 중요합니다. 각 스레드가 어떤 작업을 수행할지, 어떤 자원을 언제 접근할지 명확히 정의하면 라이브록 상태를 피할 수 있습니다. 3. **시간 제한**: 라이브록을 피하는 또 다른 방법은 작업에 시간 제한을 두는 것입니다. 스레드가 자원을 얻지 못하면 일정 시간 후에 다시 시도하도록 하는 것입니다. 이 방식은 다른 스레드가 자원을 놓아주는 것을 무한정 기다리는 상황을 피할 수 있습니다. 4. **재시도 정책**: 자원 요청이 실패하면 재시도하기 전에 일정 시간 동안 대기하도록 하는 재시도 정책을 마련할 수 있습니다. 이를 백오프 알고리즘(backoff algorithm)이라고 부르며, 이 방법은 라이브록 상태를 피하는 데 도움이 됩니다. #### 16.2.9 start와 run 메서드 [비유] start()와 run() 메서드를 '영화 감독과 배우'에 비유해볼 수 있습니다. start() 메서드는 '영화 감독' 같습니다. 영화 감독은 배우에게 언제 어떤 역할을 수행할지 지시합니다. 감독이 "액션!"이라고 외치면, 배우는 자신의 역할에 맞게 행동을 시작합니다. 이와 마찬가지로 start() 메서드는 새로운 스레드를 생성하고, 그 스레드에서 run() 메서드를 실행하도록 지시합니다. 반면에 run() 메서드는 '배우'와 같습니다. 배우는 감독의 지시에 따라 특정 역할을 수행합니다. 이와 같이 run() 메서드는 스레드의 작업을 정의합니다. run() 메서드를 직접 호출하면, 현재 스레드에서 그 작업이 실행됩니다. 즉, run() 메서드를 직접 호출하면 새로운 스레드를 생성하지 않습니다. 따라서 스레드를 생성하고 실행하려면 start() 메서드를 사용해야 하며, 스레드의 작업을 정의하려면 run() 메서드를 사용해야 합니다. [추가질문] start와 run 메서드를 사용할 때 주의할 점이 있을까요? 1. **한번만 스레드 시작하기**: `start()` 메서드는 한 스레드에 대해 한 번만 호출해야 합니다. 한 스레드에 대해 `start()`를 두 번 이상 호출하면 `IllegalThreadStateException`이 발생합니다. 2. **직접 `run()` 호출하지 않기**: `run()` 메서드를 직접 호출하면 새로운 스레드를 생성하지 않고, 현재 스레드에서 `run()` 메서드를 실행합니다. 즉, `run()`을 직접 호출하면 병렬 처리의 이점을 얻을 수 없습니다. 스레드를 생성하고 실행하려면 항상 `start()` 메서드를 사용해야 합니다. 3. **`run()` 메서드의 오버라이딩**: `run()` 메서드는 스레드의 작업을 정의하기 위해 오버라이드해야 합니다. `run()` 메서드를 오버라이드하지 않으면 스레드는 아무 작업도 수행하지 않습니다. 4. **스레드의 생명주기 이해**: `start()` 메서드를 호출하면 스레드는 실행 상태로 들어갑니다. 그러나 스레드의 실행이 끝나면 다시 시작할 수 없습니다. 즉, `run()` 메서드의 수행이 끝나면 해당 스레드는 종료 상태가 됩니다. #### 16.2.10 thread와 Runnable [비유] 스레드를 구현하는 방법은 크게 두 가지입니다. 하나는 Thread 클래스를 확장(extends)하는 것이고, 다른 하나는 Runnable 인터페이스를 구현(implements)하는 것입니다. 두 방법 중 어느 것을 선택할지는 상황에 따라 다릅니다. 이를 이해하기 쉽게 비유해보면, Thread 클래스 확장은 '집을 처음부터 지어가는 것'과 비슷하고, Runnable 인터페이스 구현은 '기존의 집에 새로운 방을 추가하는 것'과 비슷합니다. Thread 클래스를 확장하면, 스레드 자체를 새로 생성하고 제어하는 새로운 클래스를 만들게 됩니다. 즉, 처음부터 새로운 집을 지어가는 것과 같습니다. 하지만, 이 방법은 이미 다른 클래스를 확장하고 있는 클래스에서는 사용할 수 없다는 단점이 있습니다. 왜냐하면 자바는 다중 상속을 지원하지 않기 때문입니다. 반면에 Runnable 인터페이스를 구현하면, 기존의 클래스에 스레드 기능을 추가할 수 있습니다. 즉, 기존의 집에 새로운 방을 추가하는 것과 같습니다. 이 방법은 다른 클래스를 확장하고 있는 클래스에서도 사용할 수 있습니다. 또한, 인터페이스를 구현하는 것이므로 여러 인터페이스를 구현할 수 있어 유연성이 높습니다. 따라서, 이미 다른 클래스를 확장하고 있는 경우나 여러 스레드가 같은 작업을 수행해야 하는 경우에는 Runnable 인터페이스를 구현하는 것이 좋습니다. 그러나 스레드 자체의 기능을 확장하거나 제어해야 하는 경우에는 Thread 클래스를 확장하는 것이 적합할 수 있습니다. [추가질문] callable 과 FutureTask에 대해서 설명해주세요 `Callable`과 `FutureTask`는 자바의 동시성 프로그래밍에 사용되는 인터페이스와 클래스입니다. **Callable** `Callable`은 작업의 결과를 반환할 수 있는 스레드를 정의하는 데 사용되는 인터페이스입니다. `Runnable` 인터페이스와 비슷하지만, 두 가지 주요한 차이점이 있습니다. 첫째, `Callable`의 `call()` 메서드는 결과를 반환할 수 있습니다. 둘째, `call()` 메서드는 예외를 던질 수 있습니다. 이 두 가지 특성은 `Callable`을 사용하여 결과를 반환하거나 예외를 처리하는 복잡한 스레드 작업을 정의하는 데 유용하게 만듭니다. **FutureTask** `FutureTask`는 `Runnable`과 `Future` 인터페이스를 모두 구현하는 클래스입니다. `FutureTask`는 `Callable` 객체를 받아들여 `Runnable` 객체를 생성하고, 그 결과는 `Future`를 통해 얻을 수 있습니다. `FutureTask`를 사용하면 비동기 계산을 수행하고, 그 결과를 나중에 가져올 수 있습니다. `FutureTask`는 계산이 완료되면 그 결과를 내부에 저장하고, 이후에 `get()` 메서드를 호출하여 결과를 가져올 수 있습니다. 이러한 특성 덕분에 `FutureTask`는 긴 작업을 수행하는 동안 다른 작업을 계속할 수 있게 해주며, 결과가 필요할 때까지 기다릴 수 있게 해줍니다. 이는 복잡한 동시성 애플리케이션에서 매우 유용합니다. #### 16.2.11 CountDownLatch와 CycleBarrier [비유] CountDownLatch와 CyclicBarrier는 모두 자바의 동시성 프로그래밍에서 스레드 간 동기화를 관리하는 데 사용되는 클래스입니다. 두 클래스의 주요 차이점은 사용 용도와 재사용성에 있습니다. CountDownLatch는 '스타트 라인에서의 경주'와 비슷합니다. 스타트 라인에 선 모든 선수들이 출발 준비가 될 때까지 경주를 시작하지 않습니다. CountDownLatch는 특정 수의 이벤트가 발생할 때까지 하나 이상의 스레드가 기다리도록 하는 동기화 도구입니다. 이벤트의 수는 래치를 생성할 때 지정하며, 이벤트가 발생할 때마다 카운트가 감소합니다. 카운트가 0이 되면 모든 스레드가 진행됩니다. CountDownLatch는 한 번 사용하고 나면 다시 사용할 수 없습니다. 반면에 CyclicBarrier는 '여러 라운드로 이루어진 팀 게임'과 비슷합니다. 모든 팀원이 한 라운드를 끝내야 다음 라운드로 넘어갈 수 있습니다. CyclicBarrier는 모든 스레드가 특정 지점에 도달할 때까지 기다리도록 하는 동기화 도구입니다. 모든 스레드가 지정된 지점에 도달하면, 선택적으로 Runnable 작업을 실행한 후에 스레드들이 진행됩니다. CyclicBarrier는 모든 스레드가 도달할 때마다 재설정되므로 여러 번 재사용할 수 있습니다. 따라서, 단방향 동기화가 필요한 경우 CountDownLatch를, 여러 차례 반복하여 동기화가 필요한 경우 CyclicBarrier를 사용합니다. #### 16.2.12 wait와 sleep [비유] `wait()` 메서드와 `sleep()` 메서드는 모두 스레드를 일시 중단시키지만, 사용 목적과 동작 방식에는 몇 가지 중요한 차이점이 있습니다. `wait()` 메서드는 '식당에서 주문한 음식을 기다리는 고객'과 비슷하고, `sleep()` 메서드는 '알람 시계로 설정된 시간까지 잠자는 사람'과 비슷합니다. `wait()` 메서드는 객체의 모니터 락을 가지고 있는 스레드가 호출하며, 해당 스레드를 대기 상태로 만듭니다. 대기 상태인 스레드는 다른 스레드가 `notify()` 또는 `notifyAll()` 메서드를 호출할 때까지 기다립니다. 즉, '식당에서 음식을 기다리는 고객'처럼, 음식이 준비되어야만 식사를 시작할 수 있습니다. 반면에 `sleep()` 메서드는 스레드를 지정된 시간 동안 일시 중단시킵니다. 이 시간이 지나면 스레드는 자동으로 재개됩니다. `sleep()` 메서드는 객체의 모니터 락을 유지하므로, 다른 스레드는 그 객체의 동기화 메서드나 블록에 들어갈 수 없습니다. 즉, '알람 시계로 설정된 시간까지 잠자는 사람'처럼, 알람이 울리기 전까지 깨어나지 않습니다. 따라서, 스레드가 특정 조건이 만족될 때까지 기다려야 하는 경우에는 `wait()` 메서드를, 스레드를 일정 시간 동안 일시 중단시키려는 경우에는 `sleep()` 메서드를 사용합니다. [추가질문] wait 메서드는 록을 해제할 시기를 결정하는 반복문에서 호출해야하고, sleep 메서드는 반복문 안에서 호출하지 않는 것이 좋은 이유는 무엇인가요? `wait()` 메서드와 `sleep()` 메서드는 작동 방식과 목적이 다르므로, 사용하는 방식도 달라야합니다. **wait() 메서드** `wait()` 메서드는 동기화 블록이나 동기화 메서드 내에서 호출되어야 합니다. 이 메서드가 호출되면, 현재 스레드는 대기 상태가 되고, 해당 객체의 모니터 락을 해제합니다. 이는 다른 스레드가 동기화 블록이나 메서드에 접근하고 `notify()` 또는 `notifyAll()` 메서드를 호출하여 대기 중인 스레드를 깨울 수 있게 합니다. `wait()` 메서드를 반복문에서 사용하는 이유는 `notify()` 또는 `notifyAll()` 메서드 호출로 인해 스레드가 깨어나면, 깨어난 스레드가 계속 진행해도 안전한지 확인하기 위해서입니다. 즉, `wait()` 후에 깨어난 스레드가 진행 조건을 다시 검사하도록 하는 것입니다. **sleep() 메서드** `sleep()` 메서드는 현재 스레드를 지정된 시간 동안 일시 중단시킵니다. 이 메서드는 모니터 락을 보유한 상태로 유지하기 때문에, 다른 스레드들이 해당 객체의 동기화 블록이나 메서드에 접근할 수 없게 됩니다. `sleep()` 메서드를 반복문 안에서 호출하는 것은 주의해야 합니다. 만약 `sleep()`이 반복문 안에서 호출되면, 모니터 락을 오랫동안 유지하게 되어 다른 스레드들이 해당 객체에 접근하는 것을 막을 수 있습니다. 이는 성능 저하를 초래하거나, 최악의 경우 데드락(deadlock)을 일으킬 수 있습니다. 따라서, `wait()` 메서드는 조건을 만족할 때까지 스레드를 대기시키는 데 사용하고, `sleep()` 메서드는 스레드를 일정 시간 동안 일시 중단시키는 데 사용합니다. 이 두 메서드를 적절히 사용하면 동시성 프로그래밍을 효과적으로 관리할 수 있습니다. #### 16.2.13 ConcurrentHashMap과 Hashtable [비유] `ConcurrentHashMap`과 `Hashtable`은 모두 자바의 동시성 프로그래밍에서 사용되는 스레드 안전한 맵 구현체입니다. 그러나 두 클래스는 동기화 방식이 다르며, 이 차이가 성능에 큰 영향을 미칩니다. 이를 '함께 일하는 직원들'에 대한 비유로 설명해보겠습니다. `Hashtable`은 '하나의 회의실에서 모든 직원이 한 명씩 차례로 일하는' 상황과 비슷합니다. 한 직원이 일을 하는 동안, 다른 모든 직원들은 그가 일을 끝마치기를 기다려야 합니다. 즉, 한 번에 하나의 스레드만 맵에 접근할 수 있습니다. 이런 방식은 스레드 안전성을 보장하지만, 동시에 여러 스레드가 맵을 사용하려 할 때 병목 현상을 일으키므로 성능이 저하됩니다. 반면, `ConcurrentHashMap`은 '여러 개의 회의실에서 동시에 여러 직원이 일하는' 상황과 비슷합니다. 각 직원은 자신의 회의실에서 독립적으로 일할 수 있으므로, 한 직원이 일하는 동안 다른 직원들이 기다릴 필요가 없습니다. `ConcurrentHashMap`은 맵을 여러 세그먼트로 나누고, 각 세그먼트는 독립적으로 잠금 처리되므로 여러 스레드가 동시에 맵에 접근할 수 있습니다. 이런 방식은 동시성 수준을 높여 성능을 향상시킵니다. 따라서, 동시에 많은 수의 스레드가 맵을 사용해야 하는 경우에는 `ConcurrentHashMap`을 사용하는 것이 좋습니다. #### 16.2.14 ThreadLocal [비유] `ThreadLocal` 클래스는 스레드별로 데이터를 저장하고 접근할 수 있는 저장소를 제공하는 자바의 도구입니다. 각 스레드에 대해 자체 변수를 가질 수 있습니다. `ThreadLocal`은 각 직원이 자신만의 서랍을 가지는 사무실과 같습니다. 사무실에서 각 직원이 자신만의 서랍을 가지고 있다고 생각해보세요. 그 서랍에는 각 직원이 필요로 하는 문서나 물품들이 들어있습니다. 다른 직원들은 그 서랍을 열거나 그 안에 있는 무언가를 볼 수 없습니다. 이 서랍은 그 직원만의 개인 공간이며, 그 직원이 필요한 것들을 저장하고 필요할 때 꺼내 쓸 수 있게 해줍니다. 마찬가지로, `ThreadLocal`은 각 스레드가 자신만의 데이터를 저장하고, 필요할 때 그 데이터를 참조하거나 변경할 수 있는 공간을 제공합니다. 다른 스레드들은 그 공간에 접근하지 못하므로, 데이터는 스레드 안전하게 보호됩니다. 따라서 `ThreadLocal`은 스레드 간에 데이터를 공유하지 않고, 각 스레드가 독립적으로 작업을 수행하도록 해주는 도구입니다. [추가질문] ThreadLocal 클래스 구현방법에 대해서 간단하게 설명해주세요 `ThreadLocal` 클래스를 사용하여 스레드별로 데이터를 저장하고 접근하는 방법은 다음과 같습니다. 1. 먼저 `ThreadLocal` 인스턴스를 생성합니다. 이 인스턴스는 각 스레드가 독립적으로 사용할 수 있는 저장소를 제공합니다. ```java ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); ``` 2. 스레드 내에서 `ThreadLocal` 인스턴스에 값을 설정하려면 `set()` 메서드를 사용합니다. 이 메서드는 현재 스레드의 복사본에 값을 저장합니다. ```java threadLocal.set(100); ``` 3. 해당 스레드에서 저장된 값을 얻으려면 `get()` 메서드를 사용합니다. ```java Integer value = threadLocal.get(); ``` 4. 스레드가 더 이상 `ThreadLocal` 값을 필요로 하지 않는 경우, `remove()` 메서드를 호출하여 스레드별 저장소를 정리합니다. 이렇게 하지 않으면 메모리 누수가 발생할 수 있습니다. ```java threadLocal.remove(); ``` 위의 방법을 사용하면, 각 스레드는 자신만의 값을 `ThreadLocal`에 저장하고 접근할 수 있습니다. 다른 스레드는 그 값을 볼 수 없으므로, 데이터는 스레드 안전하게 보호됩니다. #### 16.2.15 submit과 execute [비유] `ExecutorService`의 `submit()` 메서드와 `Executor`의 `execute()` 메서드는 모두 작업을 스레드 풀에 제출하는데 사용되지만, 그들이 작업을 처리하고 결과를 반환하는 방식에는 차이가 있습니다. `execute()` 메서드는 '편지를 발송하는' 과정과 비슷하고, `submit()` 메서드는 '택배를 발송하고 그 추적 번호를 받는' 과정과 비슷합니다. `execute()` 메서드는 Runnable 작업을 스레드 풀에 제출하고, 작업이 완료되었는지 아니면 어떠한 예외가 발생했는지에 대한 피드백을 제공하지 않습니다. 이는 '편지를 발송하는' 과정과 비슷합니다. 편지를 우편함에 넣으면, 그 편지가 제대로 배달되었는지, 아니면 어떤 문제가 발생했는지에 대한 피드백을 받을 수 없습니다. 반면에 `submit()` 메서드는 Callable 또는 Runnable 작업을 스레드 풀에 제출하고, 작업의 결과를 확인하거나 작업이 완료되었는지 확인할 수 있는 Future 객체를 반환합니다. 이는 '택배를 발송하고 그 추적 번호를 받는' 과정과 비슷합니다. 택배를 발송하면, 우리는 택배 추적 번호를 통해 택배의 위치를 확인하거나, 택배가 제대로 배달되었는지 확인할 수 있습니다. 따라서, 작업의 결과를 확인하거나 작업이 완료되었는지 확인해야 하는 경우에는 `submit()` 메서드를 사용하고, 그런 피드백이 필요하지 않은 경우에는 `execute()` 메서드를 사용합니다. #### 16.2.16 interrupted와 isInterrupted [비유] `interrupted()` 메서드와 `isInterrupted()` 메서드는 자바의 `Thread` 클래스에서 제공하는 메서드로, 스레드가 인터럽트되었는지 확인하는데 사용됩니다. 이 두 메서드의 주요 차이점은 인터럽트 상태를 클리어(clear)하는지 여부입니다. `interrupted()` 메서드는 현재 스레드가 인터럽트되었는지 확인하고, 인터럽트 상태를 클리어합니다. 즉, 메서드가 호출된 후에는 인터럽트 상태가 초기화되어 이전에 인터럽트가 발생했던 사실을 알 수 없게 됩니다. 반면에 `isInterrupted()` 메서드는 해당 스레드가 인터럽트되었는지 확인하지만, 인터럽트 상태를 클리어하지 않습니다. 따라서 메서드 호출 후에도 인터럽트 상태는 그대로 유지됩니다. 이를 비유로 설명하면, `interrupted()` 메서드는 '알람이 울린 후에 알람을 꺼버리는' 것과 비슷하고, `isInterrupted()` 메서드는 '알람이 울린 것을 확인만 하고 알람은 계속 울리게 두는' 것과 비슷합니다. `interrupted()` 메서드를 호출하면 알람이 꺼져서 다시 확인하려면 알람이 울린 적이 없는 것처럼 됩니다. 반면에 `isInterrupted()` 메서드를 호출하면 알람 상태를 그대로 유지하므로, 계속해서 알람이 울린 것을 확인할 수 있습니다. [추가질문] 인터럽트에 대해서 설명해주세요. 인터럽트는 컴퓨터 과학에서 프로그램의 실행을 잠시 중단하고, 특정 작업(일반적으로 우선 순위가 높은 작업)을 처리하는 메커니즘을 말합니다. 이는 컴퓨터 시스템에서 중요한 이벤트가 발생했을 때 CPU에 신호를 보내어 해당 이벤트를 즉시 처리하도록 하는 방법입니다. 자바에서는 이 개념을 스레드에 적용하여, 하나의 스레드가 다른 스레드에게 작업을 중단하고 특정 동작을 수행하라는 신호를 보내는 방법을 제공합니다. 이를 '스레드 인터럽트'라고 합니다. 스레드 인터럽트는 `Thread` 클래스의 `interrupt()` 메서드를 호출하여 수행할 수 있습니다. 이 메서드를 호출하면 해당 스레드의 인터럽트 상태가 설정되고, 스레드는 이 상태를 확인하고 적절히 반응할 수 있습니다. 스레드는 `InterruptedException`을 처리하거나 `isInterrupted()` 메서드 또는 `Thread.interrupted()` 메서드를 사용하여 인터럽트 상태를 확인할 수 있습니다. 이렇게 스레드 인터럽트를 이용하면, 하나의 스레드가 다른 스레드에게 작업을 중단하거나 특정 동작을 수행하라는 신호를 보낼 수 있으므로, 복잡한 멀티스레드 애플리케이션에서 유용하게 사용됩니다. #### 16.2.17 스레드 중단 [비유] 스레드를 중지하거나 취소하는 것은 자바에서 직접적으로 지원하지 않는 작업입니다. `Thread.stop()` 메서드는 사용되지 않는 것이 권장되며, 대신 개발자는 안전한 방법으로 스레드를 중지하거나 취소하는 메커니즘을 직접 구현해야 합니다. 이는 주로 스레드 인터럽트를 이용하거나, 공유된 플래그를 체크하는 방법으로 수행됩니다. 이를 비유로 설명하자면, 스레드의 실행을 '자동차의 주행'으로 생각해볼 수 있습니다. 자동차가 달리고 있다고 할 때, 갑자기 브레이크를 밟아 차를 정지시키는 것은 위험하고 불안전한 방법입니다. 이는 `Thread.stop()` 메서드를 호출하는 것과 비슷합니다. 상태를 확인하지 않고 강제로 스레드를 중지시키면, 데이터가 불안정해지거나 예기치 않은 문제가 발생할 수 있습니다. 반면에, 안전하게 차를 정지시키려면 브레이크 신호를 보내고, 차량이 안전하게 멈출 수 있을 때까지 천천히 속도를 줄이는 것이 좋습니다. 이는 스레드 인터럽트를 이용하거나 공유된 플래그를 체크하는 방법과 비슷합니다. 스레드에게 '중지' 신호를 보내고, 스레드가 안전하게 중지할 수 있는 시점에 도달하면 실행을 멈추게 할 수 있습니다. 이런 방법으로 스레드를 안전하게 중지하거나 취소할 수 있습니다. 주요한 점은 스레드가 언제 중지되어야 하는지를 스스로 결정하게 하고, 중지 신호를 적절히 처리할 수 있도록 해야한다는 것입니다. [추가 질문] 안전하게 스레드를 중지 시키는 방안에 대해서 설명해주세요. 자바에서 스레드를 안전하게 중지시키는 방법은 일반적으로 두 가지입니다: 인터럽트를 이용하는 방법과 공유 변수를 이용하는 방법입니다. 1. **인터럽트를 이용하는 방법:** 스레드에 인터럽트를 보내어 스레드를 중지시키는 방법입니다. `Thread` 클래스의 `interrupt()` 메서드를 호출하여 스레드에 인터럽트를 보낼 수 있습니다. 그리고 실행 중인 스레드는 주기적으로 `isInterrupted()` 메서드를 호출하여 인터럽트가 발생했는지 확인하고, 인터럽트가 발생했다면 적절한 동작을 수행하여 스레드를 중지시킵니다. 2. **공유 변수를 이용하는 방법:** 공유 변수를 이용하여 스레드를 중지시키는 방법입니다. 볼티타일(volatile) 또는 원자적(atomic) 변수를 사용하여 스레드 간에 중지 신호를 전달할 수 있습니다. 실행 중인 스레드는 주기적으로 이 변수를 확인하고, 변수의 값이 변경되었다면 적절한 동작을 수행하여 스레드를 중지시킵니다. 이 두 가지 방법 모두 스레드가 언제 중지되어야 하는지를 스스로 결정하도록 하고, 중지 신호를 적절히 처리할 수 있도록 합니다. 따라서 이 방법들을 이용하면 스레드를 안전하게 중지시킬 수 있습니다. 하지만 이러한 방법들이 항상 작동하는 것은 아닙니다. 스레드가 블로킹 상태에 있거나, 중지 신호를 확인하지 않는 경우에는 이 방법들이 작동하지 않을 수 있습니다. 이 경우에는 스레드가 중지 신호를 확인하고 적절하게 반응할 수 있도록 코드를 수정해야 합니다. #### 16.2.18 스레드 사이의 데이터 공유 [비유] 스레드 간에 데이터를 공유하는 것은 여러 스레드가 동시에 같은 메모리에 접근하는 것을 의미합니다. 이는 여러 가지 방법으로 수행될 수 있으며, 공유된 변수, 동기화된 메서드, 동기화된 블록, 볼티타일 필드, 원자적 연산 등을 사용할 수 있습니다. 이를 비유로 설명하면, 여러 사람이 같은 책을 읽는 상황과 비슷합니다. 1. **공유된 변수:** 여러 사람이 같은 책을 읽을 때, 모두 같은 책을 참조하면서 읽는 것과 비슷합니다. 하지만 이 경우, 한 사람이 책의 내용을 바꾸면 다른 사람들도 그 변경된 내용을 읽게 됩니다. 이와 같이 공유된 변수는 여러 스레드가 동시에 접근할 수 있지만, 한 스레드가 변수의 값을 변경하면 다른 스레드에게도 그 변경사항이 반영됩니다. 2. **동기화된 메서드나 블록:** 여러 사람이 같은 책을 읽을 때, 한 사람이 책을 읽는 동안 다른 사람은 기다려야 하는 것과 비슷합니다. 한 사람이 책을 다 읽은 후에 다음 사람이 책을 읽을 수 있습니다. 이와 같이 동기화된 메서드나 블록은 한 번에 한 스레드만이 해당 메서드나 블록의 코드를 실행할 수 있도록 합니다. 3. **볼티타일 필드와 원자적 연산:** 여러 사람이 같은 책을 읽을 때, 각각의 사람이 책의 내용을 바꾸지 않고, 책의 내용이 항상 최신 상태임을 보장하는 것과 비슷합니다. 이와 같이 볼티타일 필드와 원자적 연산은 여러 스레드가 동시에 데이터를 읽거나 쓸 수 있도록 하면서도 데이터의 일관성을 보장합니다. 이런 방법으로 스레드 간에 데이터를 공유할 수 있지만, 동시성 문제를 주의해야 합니다. 여러 스레드가 동시에 데이터를 읽거나 쓰려고 하면 데이터의 일관성이 깨질 수 있으므로, 적절한 동기화 기법을 사용해야 합니다. [추가질문] BlockingQueue 인터페이스, LinkedBlockingQueue클래스와 ConcurrentLinkedDeque 클래스에 대해서 설명해주세요. **BlockingQueue 인터페이스:** 이 인터페이스는 Java에서 제공하는 Queue 인터페이스의 확장 버전으로, 스레드 안전한 동시성 큐를 제공합니다. BlockingQueue는 큐가 비어있을 때 요소를 가져오려는 시도를 차단하고, 큐가 가득 찼을 때 요소를 추가하려는 시도를 차단하는 특성을 가지고 있습니다. 이는 생산자-소비자 문제를 해결하는 데 효과적인 방법으로, 대기열이 비어있을 때 소비자가 기다리게 하고, 대기열이 가득 찼을 때 생산자가 기다리게 합니다. **LinkedBlockingQueue 클래스:** LinkedBlockingQueue는 BlockingQueue 인터페이스를 구현하는 클래스로, 선택적으로 용량 제한을 가질 수 있는 FIFO(First-In-First-Out) 데이터 구조를 제공합니다. LinkedBlockingQueue는 내부적으로 연결된 노드를 사용하여 요소를 저장합니다. 큐의 용량이 설정되지 않았다면, 큐의 용량은 Integer.MAX_VALUE가 됩니다. 이 클래스는 동시성 프로그래밍에서 생산자-소비자 문제를 해결하는 데 매우 유용합니다. **ConcurrentLinkedDeque 클래스:** ConcurrentLinkedDeque는 Deque 인터페이스를 구현하는 클래스로, 동시성 프로그래밍을 위한 스레드 안전한 양방향 큐를 제공합니다. 이 클래스는 내부적으로 연결된 노드를 사용하여 요소를 저장하며, FIFO 뿐만 아니라 LIFO(Last-In-First-Out) 데이터 구조를 제공합니다. ConcurrentLinkedDeque는 동시에 여러 스레드에서 안전하게 접근할 수 있도록 설계되어 있습니다. 이렇게 BlockingQueue 인터페이스, LinkedBlockingQueue 클래스, ConcurrentLinkedDeque 클래스는 동시성 프로그래밍에서 데이터를 안전하게 공유하고 관리하는 데 사용되는 자료구조를 제공합니다. #### 16.2.19 ReadWriteLock [비유] ReadWriteLock은 자바에서 동시성 프로그래밍을 지원하기 위해 제공하는 도구 중 하나입니다. 이는 읽기 작업과 쓰기 작업에 대해 별도의 락을 제공하여, 동시에 여러 읽기 작업을 허용하면서 쓰기 작업은 독점적으로 수행하는 것을 가능하게 합니다. ReadWriteLock은 도서관에서 책을 읽거나 대출하는 상황과 비슷합니다. 1. **읽기 락(Read Lock):** 도서관에서 여러 사람이 동시에 같은 책을 읽을 수 있는 것처럼, ReadWriteLock에서는 여러 스레드가 동시에 데이터를 읽을 수 있습니다. 읽기 락은 공유 락이므로, 한 스레드가 읽기 락을 보유하고 있는 동안 다른 스레드도 읽기 락을 얻을 수 있습니다. 2. **쓰기 락(Write Lock):** 도서관에서 한 사람이 책을 대출하면, 그 사람이 책을 반납하기 전까지 다른 사람은 그 책을 대출하거나 읽을 수 없는 것처럼, ReadWriteLock에서는 한 스레드가 쓰기 락을 보유하고 있는 동안 다른 스레드는 읽기 락이나 쓰기 락을 얻을 수 없습니다. 쓰기 락은 독점 락이므로, 한 스레드가 쓰기 락을 보유하고 있는 동안 데이터에 대한 독점적인 접근 권한을 가집니다. 이런 방식으로 ReadWriteLock는 데이터를 안전하게 공유하고 관리하는 데 도움을 줍니다. 읽기 작업이 더 많은 경우에 ReadWriteLock을 사용하면 프로그램의 성능을 향상시킬 수 있습니다. #### 16.2.20 생산자-소비자 [비유] 자바에서의 생산자-소비자 문제는 멀티스레딩 환경에서 자주 발생하는 동기화 문제입니다. 이는 두 개 이상의 스레드가 공유 자원에 동시에 접근하려고 할 때 발생하는데, 특히 하나 또는 여러 스레드(생산자)가 데이터를 생성하고 다른 스레드(소비자)가 그 데이터를 사용하려고 할 때 발생합니다. 이를 비유로 설명하면, 레스토랑에서 요리사(생산자)와 손님(소비자) 사이의 상호작용과 비슷합니다. 1. **요리사(생산자)**는 음식을 만들어 서빙대에 놓습니다. 만약 서빙대가 가득 차 있다면 요리사는 음식을 더 이상 만들지 않고 기다려야 합니다. 마찬가지로, 생산자 스레드는 데이터를 생성하고 버퍼(공유 자원)에 넣습니다. 버퍼가 가득 차 있으면 생산자 스레드는 데이터를 더 이상 생성하지 않고 대기합니다. 2. **손님(소비자)**은 서빙대에서 음식을 가져가 먹습니다. 만약 서빙대에 음식이 없다면 손님은 음식이 나올 때까지 기다려야 합니다. 이와 비슷하게, 소비자 스레드는 버퍼에서 데이터를 가져와 사용합니다. 버퍼에 데이터가 없으면 소비자 스레드는 데이터가 생성될 때까지 대기합니다. 이렇게 생산자와 소비자 사이의 적절한 조율이 이루어져야 합니다. 이를 위해 자바에서는 동기화 도구를 제공합니다. 예를 들어, `synchronized` 키워드를 사용하여 공유 자원에 대한 동시 접근을 제어하거나, `wait()`와 `notify()` 메서드를 사용하여 생산자와 소비자 사이의 통신을 조율할 수 있습니다. 또한, `BlockingQueue`와 같은 자료구조를 사용하면 생산자-소비자 문제를 효율적으로 해결할 수 있습니다.