# 자바 동시성 프로그래밍 - Java Synchronization

## 교착 상태 (DeadLock)

* **DeadLock** 이란 프로세스나 스레드들이 서로가 소유하고 있는 자원을 기다리며 무한히 대기하고 있는 상태를 말한다.
* 교착 상태에서는 아무런 진전도 이루어지지 않아 작업이 진행되지 않는 문제가 발생한다.
* 이것은 동일한 환경과 코드에서 발생할 수도 있고 발생하지 않을 수도 있다.

**대표적인 DeadLock 예시로 "식사하는 철학자"가 있다.**

![img\_38.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-b455f4c9112f7546c7bf2bcf2f7bc95e61e5bfe1%2Fimg_38.png?alt=media)

***

## 데드락 발생 조건

* 데드락은 다음 네 가지 필요 조건을 동시에 만족할 때 발생한다.
* 한 가지라도 만족하지 않으면 데드락이 발생하지 않는다.

### 1. 상호 배제 (Mutual Exclusion)

* 자원은 한 번에 하나의 스레드만 사용할 수 있다.
  * 철학자는 한 번에 하나의 젓가락을 사용할 수 있으므로 상호 배제를 구현하여 젓가락을 동시에 두 명 이상의 철학자가 사용하지 못하도록 한다.

### 2. 점유 대기 (Hold and Wait)

* 스레드가 최소한 하나의 자원을 보유한 상태에서 다른 자원을 기다리고 있다.
  * 철학자들은 젓가락을 기다리는 상태에서 무한히 대기할 수 있으며, 다른 철학자가 젓가락을 반납하기를 기다릴 수 있다.

### 3. 비선점 (No Preemption)

* 자원을 할당 받은 스레드가 자원을 스스로 반납하기 전까지 자원을 강제로 빼앗을 수 없다.
  * 철학자들은 젓가락을 사용하는 동안 다른 철학자에 의해 젓가락이 강제로 반납 될 수 없으며, 젓가락은 해당 철학자에 의해 반납될 때까지 보유된다.

### 4. 순환 대기 (Circular Wait)

* 각 스레드는 순환적으로 다음 스레드가 요구하는 자원을 가지고 있어 사이클이 형성된다.
  * 각 철학자는 서로 다른 두 개의 젓가락을 사용해야 하므로 순환적으로 다음 철학자가 요구하는 젓가락을 가지고 있을 수 있어 사이클이 형성된다.

***

## 데드락 예

### 1. 락 순서에 의한 데드락

![img\_39.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-c6d14844da24b01cd0be5c5159a29c78fa58636f%2Fimg_39.png?alt=media)

![img\_45.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-e25f7623e80413c90cfb6fd8dc2f268b44f12819%2Fimg_45.png?alt=media)

![img\_46.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-b83f56f13af73246c9e0fd78e80b84ac4e55251a%2Fimg_46.png?alt=media)

* 프로그램이 종료되지 않고 무한 대기에 빠지게 된다.

### 2. 동적인 락 순서에 의한 데드락

![img\_40.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-2581821965a4aaf45ede5fbed6117eb410de2ed0%2Fimg_40.png?alt=media)

![img\_47.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-e0095b947c2e2a4e5fb8dfe9010591d843f0ed73%2Fimg_47.png?alt=media)

![img\_48.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-e90e6af322cb0793efa72913592f41377e44de09%2Fimg_48.png?alt=media)

* 프로그램이 종료되지 않고 무한 대기에 빠지게 된다.

### 3. 객체 간의 데드락

![img\_41.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-01f49e8fcb5d187627cbccd8fe8f2426fbb2d522%2Fimg_41.png?alt=media)

![img\_49.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-2dd5a9919cf6fcfef7aa5e2a2900b05a11995507%2Fimg_49.png?alt=media)

![img\_50.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-77b02c735f56ac7203fee4fee7ec7453271712c5%2Fimg_50.png?alt=media)

* 프로그램이 종료되지 않고 무한 대기에 빠지게 된다.

***

## 데드락 방지와 원인 추적

* 데드락 방지는 데드락 발생 조건 네 가지 중에서 최소한 1가지를 방지함으로서 네 가지를 모두 만족하지 않게 하는 기법이다.
* 일단 데드락이 발생하면 애플리케이션 단에서 데드락을 해소하는 것은 어렵고 서버를 재기동하거나 종료하는 것 밖에 현실적으로 다른 해결책은 없다.

### 1. 한번에 하나 이상의 락을 사용하지 않는다.

* 데드락은 스레드가 락을 중첩으로 제어하면서 발생하는 경우가 많기 때문에 가능한 한 스레드가 두 개 이상의 락을 제어하는 상황을 만들지 않도록 하는 것이 좋다.

### 2. 락의 순서를 잘 조정한다.

* 불가피하게 여러 개의 락을 사용해야 한다면 락의 점유 순서를 일정한 순서로 정해주도록 함으로서 데드락이 발생할 수 있는 조건 중 하나의 순환 대기를 방지하도록 한다.

![img\_42.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-79ec1f12dbda2266ffed62171e2051eb5dfaa39c%2Fimg_42.png?alt=media)

![img\_51.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-43ab15c34b0856e1732ad72f2446b13ab65bf108%2Fimg_51.png?alt=media)

![img\_52.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-496d6e16bc564e82f9f3b8f61aee2285b89315fc%2Fimg_52.png?alt=media)

### 3. 락 타임아웃을 건다.

* 락을 요청할 때 일정 시간 이내에 락을 얻지 못하면 다른 작업을 수행하도록 타임아웃을 설정한다.
* 락 획득에 타임아웃 오류가 나면 오래 기다리지 않고 제어권이 다시 돌아오기 때문에 현재 소유한 락을 해제하고 잠시 기다리다가 데드락 상황이 지나가면 다시 정상으로 동작할 수 있다.

![img\_43.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-c3d4c6313385e7561b2fa43265aeec6df01381ad%2Fimg_43.png?alt=media)

### 4. 메서드는 오픈 호출 형태로 구현한다.

* 락을 전혀 확보하지 않은 상태에서 메서드를 호출하는 것을 오픈 호출 이라고 하며, 락을 전체 메서드에 적용하지 않고 락이 필요한 임계 영역만 보호하도록 한다.
* 여러 개의 락을 호출하더라도 동시에 락을 점유하는 것이 아닌 순차적으로 락을 획득하고 해제하는 방식으로 메서드를 호출하도록 한다.

![img\_44.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-b369f6cf4bd6dffe878eabb468eeaf56c4d7e0fd%2Fimg_44.png?alt=media)

![img\_53.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-a4341d0678947b09a2cdf45c88a54a2ae33e848e%2Fimg_53.png?alt=media)

![img\_54.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-8f4cab0ccbd5e7f6d36a603ddb387081694b5554%2Fimg_54.png?alt=media)

### 5. 스레드 덤프를 활용한다.

* 스레드 덤프에는 실행 중인 스레드의 모든 스택 트레이스가 담겨져 있고 락과 관련된 정보도 포함되어 있기 때문에 이를 활용해서 어디에서, 어떤 스레드가, 어느 시점에, 왜 데드락이 발생했는지 원인을 추적 및 분석하고 해결 방안을 모색할 수 있다.

***

## 그 외 활동성 문제

### 1. 기아 상태 (Starvation)

* 멀티 스레드 환경에서 한 스레드가 자원을 계속해서 얻지 못하여 영원히 대기하는 상태를 말한다.
* 다른 스레드들이 우선적으로 자원을 점유하거나 실행되어 해당 스레드가 자원을 얻지 못하게 되면 기아 상태가 발생한다.
* 기아 상태를 해결하기 위해서는 우선순위를 기반으로 한 스케줄링이 아니라 공정성을 고려하여 모든 스레드가 공평하게 실행될 수 있도록 우선순위를 조정하는 등의 설계가 뒷받침 되어야 한다.

### 2. 라이브락 (Livelock)

* 데드락과 비슷한 상태로, 여러 스레드가 서로의 자원을 기다리면서 진행이 지연되는 상태를 말한다.
* 라이브락은 데드락과는 다르게 작업이 멈추지 않고 계속해서 진행되지만, 실질적인 작업이 진행되지 않거나 진행이 제대로 이루어지지 않는 상태를 의미한다.
* 라이브락 상태에서는 스레드가 서로의 자원을 기다리기 때문에 작업이 충분히 느려질 수 있으며, 서로가 양보하는 동작이 반복되면서 진행이 지연되는 상태가 발생한다.

![img\_55.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-76678d4fc20f69324fef6b851cfd839f3136634c%2Fimg_55.png?alt=media)

![img\_56.png](https://2900387690-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOjEy54f4ToLNl5n9gmZs%2Fuploads%2Fgit-blob-8a14cc4bb118da7859b65b62fb1ffcdb0819f9ee%2Fimg_56.png?alt=media)

* 서로 양보하기만 하고 프로그램은 종료되지 않는다.

***

[이전 ↩️ - Java Synchronization - `volatile`(메모리 동기화)](https://github.com/genesis12345678/TIL/blob/main/Java/reactive/synchronization/javaSync/volatile.md)

[메인 ⏫](https://github.com/genesis12345678/TIL/blob/main/Java/reactive/Main.md)

[다음 ↪️ - Java Locks - Lock & ReentrantLock](https://github.com/genesis12345678/TIL/blob/main/Java/reactive/locks/ReentrantLock.md)
