: 정렬되어있는 수를 양쪽 끝에서부터 투 포인터로 찾아가며, 절댓값이 0이랑 가장 가까운 수를 구하는 문제. 투포인터 기본문제라고 생각함
2. 백준 20366 - 같이 눈사람 만들래?
: 눈덩이를 정렬한 후에, 한 눈사람을 만들 두 개의 눈덩이를 구함. 이 후 그 사이에서 투포인터를 이용해서 그 눈사람과 가장 작은 차이가 나는 눈사람을 구함. 문제를 풀기위한 핵심은 두 눈덩이를 정하고, 거기서 정답이 나올 수 있는 것은 두 눈덩이 사이에서 눈사람을 만들 경우라는 것.
* synchronized 블럭에 들어갔더라도, 조건이 맞지 않은 경우 lock을 풀고 wait set에 들어가고 블럭 탈출
* 다른 스레드가 synchronized 실행 후 notify로 wait set에 있는 하나를 깨워서 entry set에 넣음
3. 예시를 보며 이해
- Producer 1번, 2번을 P1, P2로 하고, Consumer 1,2,3번을 C1, C2, C3라 하자
- 그림의 Buffer는 한번에 한 스레드만 사용해야 함
- 진행
① C1이 Buffer(Buffer의 Lock) 획득. 하지만, Buffer가 비어있으므로(실행할 수 없는 조건) C1은 wait set에 진입
② P1, P2, P3가 모두 Buffer를 획득하기를 원해서 P1만 Buffer 획득. 나머지는 entry set에 진입
③ P1은 Buffer하나를 채운 후 notify -> wait set에 있는 하나를 깨움 -> C1은 entry set에 진입
④ entry set에 있는 P2, P3, C1 중 하나가 Buffer 획득
⑤ ... 반복
- wait set중 어떤 것을 깨울 것인가? entry set중 어떤 것에 Lock을 줄 것인가? -> 이러한 문제는 starvation을 야기할 수 있음.
- 또한, wait set에 Producer와 Consumer가 같이 관리되기 때문에 만약 Producer가 Buffer를 가득 채우고 wait set에 있는 다른 Producer를 깨우면, 이 Producer는 Buffer를 획득하더라도 채울 수 없기 때문에 또 wait set에 들어감. Producer가 Buffer를 채웠다면, wait set에 있는 Consumer를 깨우는게 더 효율적인 상황임
4. Java에서 Lock & Condition 방법
- 위와 같은 문제를 해결하기 위해 Lock & Condition 방법을 제공함
- Monitor 방법과 다른 점은 Condition에 따라 wait set을 따로 구분하는 것임. 위 문제에서는 Producer wait set과 Consumer wait set을 따로 구분함
- 만약 P1이 Buffer를 채웠다면, Consumer wait set중 하나를 깨워서 entry set에 넣음
- 반대로 C1이 Buffer를 소비했다면, Producer wait set중 하나를 깨워서 entry set에 넣음
- 이렇게 스레드의 상태에 따라 종류를 나눠서 wait set을 만들 경우, 더 효율적인 상황이 됨
- 하지만, 같은 종류의 스레드(ex. P1, P2)끼리의 경쟁은 있기 때문에 기아현상이 발생할 가능성은 여전히 있고, Entry Set중 어떤 스레드에 lock을 줄 것인지에 대한 문제도 있기 때문에 기아현상 여전히 존재
- 공정하게 lock을 주기 위해서 java에서는 Lock lock = new ReentrantLock(boolean fair) 라는 공정성 매개변수를 제공하는데(true일 경우 오래 기다린 스레드에게 lock을 획득하게함), 그런데 그만큼 성능이 떨어지고, 대부분의 경우 공정함보다는 성능을 채택함
Java에서 Programming Level에서의 동기화 문제를 어떻게 해결하는지 알아볼 수 있었습니다.
① 상호 배제 : 한 리소스는 한번에 한 프로세스(스레드)만 사용할 수 있음. 동시에 같이 사용할 수 없음.
② 점유 대기 : 한 리소스를 점유하고 다른 프로세스(스레드)가 사용하는 리소스를 기다리는 상태.
③ 비선점 : 다른 프로세스가 사용하는 리소스를 강제로 빼앗을 수 없음.
④ 순환 대기 : 프로세스들이 순환 형태로 서로의 자원을 기다리고 있는 상태.
3. 데드락 해결 방법
- 해결 방법은 크게 3가지로 나누어 볼 수 있음. 예방, 회피, 탐지 & 회복.
- 예방은 데드락 발생 조건 중 하나라도 만족하지 못하게 해서, 데드락의 발생을 예방하는 방법.
- 회피는 자원을 할당하기 전에, 할당하더라도 데드락이 발생하지 않는지 확인 후 자원을 할당하는 방법.
- 탐지 & 회복은 데드락이 발생하게끔 둔 다음, 회복하는 방법.
- 예방
① 상호 배제를 부정 : 리소스를 같이 사용할 수 있게끔 만들어야함. 그렇지만, 이럴 경우 동기화 문제 발생. 사실상 불가능한 방법
② 점유 대기를 부정 : 사용할 리소스를 모두 얻은 후에 진행 가능. 대기하는 상황을 없애는 것임. 하지만, 지금 당장 사용하지 않는 리소스를 가져가는 바람에 다른 프로세스는 이 리소스를 사용하지 못함. 리소스 사용효율이 떨어짐. 또한, 사용할 리소스가 인기가 많아서 한꺼번에 확보하기가 쉽지 않다면, 진행할 수 없음.
③ 비선점을 부정 : 다른 프로세스가 리소스를 강제로 빼앗을 수 있게 만듬.(기아현상 발생 가능할 듯)
④ 순환 대기를 부정 : 자원에 번호를 붙이고, 번호가 작은 것부터 요청할 수 있도록 하는 것
- 회피
① 리소스를 요청했을 때, 데드락이 발생할 가능성이 있는지 확인 후에 없으면 할당하는 방법
② 리소스를 요청할 때마다 특별한 알고리즘으로 매번 확인해야하는 오버헤드가 발생함
③ 미리 어떤 자원을 요구할 지 알아야 함
- 탐지 & 회복
① 자원 할당 상태를 파악하여, 데드락이 발생했는지 탐지한 후에, 데드락을 해결(회복)하는 방법
② 데드락을 탐지하는 방법은 자원 할당 그래프 등을 통해서 알 수 있음
③ 회복하는 방법은 한 프로세스씩 종료하거나 아니면 일시적으로 리소스의 선점을 가능하도록 함
④ '어떤 프로세스를 종료' 하거나 '어떤 프로세스가 리소스를 선점' 할 때, '어떤 프로세스'를 정해야하는데 이 때 특정 프로세스만 자원을 할당받아 다른 프로세스는 '기아현상'이 벌어지지 않도록 해야함
⑤ 기아현상을 해결하기 위해 리소스를 오래 대기할 수록 우선순위를 높여주는 방식을 채택할 수 있음