고급 동기화 -concurrent.Lock

이동건 (불꽃냥펀치)·2025년 1월 1일
0

Lock Support

Synchronized의 단점

  • 무한 대기: BLOCKED 상태의 스레드는 락일 풀릴때 까지 무한 대기한다.
    • 특정 시간까지만 대기하는 타임아웃 x
    • 중간에 인터럽트 x
  • 공정성: 락이 돌아왔을 때 BLOCKED 상태의 여러 스레드 중에 어떤 스레드가 락을 획득할지 알 수 없다. 최악의 경우 특정 스레드가 너무 오랜기간 락을 획득하지 못할 수 있다.

자바에서는 이 같은 문제를 해결하기 위해 여러 라이브러리를 제공하는데, 그중에서 가장 기본인 LockSupport를 사용해보자.


LockSupport 기능

LockSupport는 스레드를 Waiting상태로 변경한다.
Waiting 상태는 누가 깨워주기 전까지는 계속 대기한다. 그리고 CPU 실행 스케줄링에 들어가지 않는다.

  • park(): 스레드를 Waiting 상태로 변경한다.
  • parkNanos(nanos):스레드를 나노초 동안만 Timed_Waiting상태로 변경한다.
  • unpark(thread): Waiting상태의 대상 스레드를 Runabble상태로 변경한다.
public class LockSupportMainV1 {
     public static void main(String[] args) {
         Thread thread1 = new Thread(new ParkTask(), "Thread-1");
         thread1.start();
// 잠시 대기하여 Thread-1이 park 상태에 빠질 시간을 준다. sleep(100);
        log("Thread-1 state: " + thread1.getState());
        log("main -> unpark(Thread-1)"); LockSupport.unpark(thread1); //
      }
     static class ParkTask implements Runnable {
         @Override
		public void run() { 
          log("park 시작");
          LockSupport.park();
          log("park 종료, state: " + Thread.currentThread().getState()); 
          log("인터럽트 상태: " + Thread.currentThread().isInterrupted());
		}
	}
}
  • 메인 스레드가 Thread-1start()하면 Thread-1Runnable상태가 된다.
  • Thread-1Thread.park()를 호출한다. Runnable->Waiting상태가 되면서 대기한다.
  • 메인 스레드가 Thread-1unpark()로 깨운다. Thread-1은 대기 상태에서 실행 가능 상태로 변한다.
    • Waiting->Runnable상태로 변한다.



시간대기

  • parkNanos(nanos):스레드를 나노초동안만 Timed_Waiting 상태로 변경한다. 지정한 나노초가 지나면 Timed_Waiting상태에서 빠져나와 Runnable상태로 변경된다.


BLOCKED vs WAITING

  • 인터럽트

    • Blocked상태는 인터럽트가 발생해도 대기 상태를 빠져나오지는 못한다. 여전히 Blocked상태이다.
    • Waiting,Tiemd_Waiting 상태는 인터럽트가 걸리면 대기상태를 빠져나와 Runnable
      상태로 변한다.
  • 용도

    • Blocked 상태는 자바의 synchronized에서 락을 획득하기 위해 대기할 때 사용된다.
    • Waiting,Tiemd_Waiting상태는 스레드가 특정 조건이나 시간동안 대기할 때 발생하는 상태이다.
    • Waiting상태는 Thread.join(),LockSupport.park(),Object.wait()같은 메서드 호출시 Waiting상태가 된다.




Blocked,Waiting,Tiemd_Waiting 상태 모두 스레드가 대기하며 실행 스케줄링에 들어가지 않기 때문에 CPU 입장에서 보면 실행하지 않는 상태이다.

  • BLOCKED 상태는 synchronized 에서만 사용하는 특별한 대기 상태라고 이해하면 된다.
  • WAITING , TIMED_WAITING 상태는 범용적으로 활용할 수 있는 대기 상태라고 이해하면 된다.



ReentrantLock

package java.util.concurrent.locks;
public interface Lock {
     void lock();
     void lockInterruptibly() throws InterruptedException;
     boolean tryLock();
     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
     void unlock();
     Condition newCondition();
}

Lock 인터페이스는 동시성 프로그래밍에서 쓰이는 안전한 임계 영역을 위한 락을 구현하는데 사용된다.

Lock 인터페이스는 아래와 같은 메서드를 제공하며 대표적인 구현체로는 ReebtrantLock이 있다.


void lock()

  • 락을 획득한다. 만약 다른 스레드가 이미 락을 획득했다면, 락이 풀릴때까지 현재 스레드는 대기(Waiting)한다. 이 메서드는 인터럽트에 응답하지 않는다.

void lockInterruptibly()

  • 락 획득을 시도하되, 다른 스레드가 인터럽드 할 수 있도록 한다. 만약 다른 스레드가 이미 락을 획득했다면, 현재 스레드는 락을 획득할 때까지 대기한다. 대기중에 인터럽트가 발생하면 InterruptedException이 발생하며 락 획득을 포기한다.

boolean tryLock()

  • 락 획득을 시도하고 즉시 성공 여부를 반환한다. 만약 다른 스레드가 이미 락을 획득했다면 false를 반환하고, 그렇지 않으면 락을 획득하고 true를 반환한다.

boolean tryLock(long time, TimeUnit unit)

  • 주어진 시간동안 락 획득을 시도한다. 주어진 시간안에 락을 획득하면 true를 반환한다. 주어진 시간이 지나도 락을 획득하지 못한 경우 false를 반환한다. 이 메서드는 대기 중 인터럽트가 발생하면 InterruptedException이 발생하며 락 획득을 포기한다.

void unlock()

  • 락을 해제한다. 대기 중인 스레드 중 하나가 락을 획득 할 수 있게 된다.
  • 락을 획득한 스레드가 호출하지 않으면 IllegalStateException이 발생할 수 있다.


    Lock 인터페이스가 제공하는 다양한 기능 덕분에 synchronized의 단점인 무한 대기 문제는 해결되었지만 공정성에 관한 문제가 남아있다.

이런 공정성 문제는 ReentrantLock(true)이 해결해준다.

사용예시

import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockEx { // 비공정 모드 락
      private final Lock nonFairLock = new ReentrantLock(); // 공정 모드 락
      private final Lock fairLock = new ReentrantLock(true);
     public void nonFairLockTest() {
         nonFairLock.lock();
         try {
         
			// 임계 영역 
            
		} finally {
             nonFairLock.unlock();
         }
	}
     public void fairLockTest() {
         fairLock.lock();
		 try {
         
			// 임계 영역
            
         } finally {
             fairLock.unlock();
		} 

	}
}

ReentrantLock은 공정성 모드와 비공정 모드로 설정 할 수 있으며, 두 모드는 락을 획득하는 방식이 다르다.

비공정 모드 특징

  • 성능 우선: 락을 획득하는 속도가 빠르다.
  • 선점 기능: 새로운 스레드가 기존 대기 스레드보다 먼저 락을 획득할 수 있다.
  • 기아 현상 가능성: 특정 스레드가 계속해서 락을 획득하지 못할 수 있다.

공정 모드

  • 공정성 보장: 대기 큐에서 먼저 대기한 스레드가 락을 먼저 획득한다.
  • 기아현상 방지: 모든 스레드가 언젠가 락을 획득할 수 있다.
  • 성능 저하: 락을 획득하는 속도가 느려질 수 있다.








출처:https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EA%B3%A0%EA%B8%89-1/dashboard

profile
자바를 사랑합니다

0개의 댓글

관련 채용 정보