Java/병렬 프로그래밍

자바 병렬 프로그래밍 - 명시적인 락

kwon92 2021. 1. 21. 18:40

자바 5.0 전까지는 여러 스레드가 접근 하려 할 때 조율 할 방법이

synchronized 방법과 volatile 키워드 뿐이었다.

 

5.0에서 추가된 ReentrantLock 은 암묵적인 락으로 할 수 없는 일도 처리 할 수 있는 고급 기능을 갖고 있다.

 

 

Lock과 ReentrantLock

 

Lock 인터페이스는 여러 가지 락 관련 기능에 대한 추상 메소드를 정의하고 있다.

암묵적인 락과 달리 조건 없는 락, 폴링 락, 타임아웃이 있는 락, 락 확보 대기 상태에 

인터럽트를 걸 수 도 있다.

 

ReentrantLock 은 이 Lock 인터페이스를 구현한다.

synchronized 구문과 동일한 메모리 가시성과 상호 배제 기능을 제공한다.

ReentrantLock 을 확보하는건 synchronized 블록에 진입하는것과 동일한 효과를 갖는다.

 

이미 암묵적인 락이 있는데 명시적인 락을 왜쓸까?

락을 확보하고자 대기하고 있는 상태의 스레드에 인터럽트를 걸고 싶거나 

대기 상태에 들어가지 않은 상태에서 락을 확보하거나 꼭 필요한 상황이 있기 때문이다.

유연성이 높은 락이라고 할 수 있다.

 

다만 이 명시적인 락은 항상 finally에서 락을 해제 해야 한다.

명시적인 락은 블록의 실행이 끝나고 블록을 떠나는 순간 

락을 자동 해제 하지 않기 때문이다. 

꼭 해제를 시켜주자

 

 

- 폴링과 시간 제한이 있는 락 확보 방법

tryLock 메소드가 지원하는 폴링 락 확보 방법이나 시간 제한이 있는 락 확보 방법은

무조건적으로 락을 확보하는 방법보다 오류를 잡아내기 좋다

 

암묵적 락은 데드락이 발생하면 프로그램이 멈춰버리고 회복 할 수 없다.

근데 시간 제한을 두거나 폴링을 하면 확률적으로 데드락을 피할 수 있다.

 

일정 시간 이내에 락을 확보

 

- 블록을 벗어나는 구조의 락

 

암묵적인 락은 락을 확보 하고 해제하는 부분이 완벽하게 블록의 구조에 맞춰져 있다.

블록이 끝나는 시점에 자동으로 해제 된다.

 

깔끔 하지만 좀 더 복잡한 구조의 프로그램에서는 락을 유연하게 사용해야 할 수 있다.

 

 

성능에 대한 고려 사항

 

ReentrantLock 은 암묵적인 락에 비해 훨씬 나은 경쟁 성능을 보여준다.

락과 관련한 스케줄링을 하는라 컴퓨터의 자원을 많이 쓸수록 어플리케이션이 쓸 수 있는 자원은 줄어든다.

잘 만들어진 동기화 기법일 수록 시스템 호출을 적게사용해서

컨텍스트 스위치 횟수 줄이고

시간을 많이 소모 하는 작업을 줄여준다.

 

다만 자바 6에서 암묵적인 락의 알고리즘이 향상되면서 서로 비슷하게 되었다.

 

공정성

ReentrantLock 은 두가지 공정성 설정을 지원한다.

하나는 불공정 락이고 다른 건 공정 락이다.

공정 방법은 순서를 지켜가며 락을 확보한다.

불공정 락은 순서 뛰어넘기가 일어나는데 락을 확보하려고 대기 하고 있는 큐에

대기 스레드가 있어도 해제된 락이 대기자 목록을 뛰어 넘어 락을 확보 할 수 있다.

(항상 뛰어넘는건 아니고 뛰어넘지 못하게 제한하지 않는것 뿐이다)

 

공정 락과 불공정 락의 성능 비교

 

락을 공정하게만 처리하면 스레드를 반드시 멈추고 다시 실행 시키는 동안에 성능에 문제가 있을 수 있다.

대기 상태에 있던 스레드가 다시 실행 상태로 돌아가고 또한 실제로 실행 되기 까지 상당한 시간이 걸리기 때문에

이 시간에 다른 스레드가 들어와서 실행을 하게 되면 처리량이 늘어나게 된다.

 

Synchronized 또는 ReentrantLock 선택

암묵적인 락은 여전히 상당한 장점을 갖고 있다.

코드에 표현방법도 훨씬 익숙하고 간결 하며 대다수의 프로그램들이 암묵적 락을 사용하고 있고,

오히려 암묵적 락과 명시적 락을 섞어 쓰면 혼동만 되며 오류 발생 가능성도 커진다.

 

ReentrantLock 은 암묵적 락으로는 해결 못하는 복잡한 상황에서만 사용하기 위한 고급 동기화 작업이다.

 

다음의 경우에 해당될때만 사용하도록하자

 

- 락을 확보할 때 타임아웃을 지정해야 하는 경우

- 폴링의 형태로 락을 확보하는 경우

- 대기 상태에 인터럽트를 걸어야 하는 경우

- 큐 처리 방법을 공정하게 해야 하는 경우

- 코드가 단일 블록의 형태를 넘어서는 경우

 

그 외의 경우에는 Synchronized 를 사용하자

 

읽기- 쓰기 락

ReentrantLock 은 상호 배제 락을 구현하고있다.

즉 한시점에 하나의 스레드만 사용하는 건데 너무 엄격하다

보통은 읽기 연산이 많이 발생하고 쓰기는 적게 발생하는데

이런 경우에는 읽기 연산은 다중 스레드가 동시에 실행 할 수 있게하면 성능을 크게 높일 수 있다.

이때 사용할 수 있는게 읽기 - 쓰기 락이다.

 

 

여러개의 읽기 작업은 동시에 처리 할 수 있고 쓰기 작업은 한 스레드만 할 수 있다.

ReadWriteLock 은 특정상황에서 병렬 프로그램의 성능을 크게 높일 수 있지만,

복잡도가 약간 높기 때문에 최적화된 상황이 아닌 곳에서 쓰면 상호 배제하는 일반적인 락에 비해 오히려 성능이 떨어지기도 한다.

 

 

요약

명시적으로 lock 클래스를 사용해 스레드를 동기화하면 암묵적인 락보다 더 많은 기능을 쓸 수 있다.

그렇다고해서 synchronized 구문 대신 기계적으로 ReentrantLock 을 사용 할 필요는 없고

꼭 필요한 경우에만 사용하도록 하자