반응형

쓰레드의 동기화(synchronization)

  • 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
  • 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화'가 필요

멀티 쓰레드 프로세스에서는 여러 프로세스가 같은 자원을 공유하기 때문에 메모리도 공유한다.
그래서 어떤 한 쓰레드가 작업하던 것을 마치지 못하고 다른 쓰레드로 차례가 넘어갔을 때,
그 쓰레드가 다른 쓰레드의 작업에 영향을 줄 수 있다.

그래서 작업이 끝나지 않은 경우에 다른 쓰레드가 그 작업을 방해하지 않도록 하려면 "동기화"가 필요하다.

동기화란, 어떤 쓰레드가 진행중인 작업을 다른 쓰레드가 간접하지 못하게 막는 것이다.

  • 동기화하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정

그래서 동기화를 하려면, 다른 쓰레드에게 간섭받지 않아야 하는 문장들을 하나의 영역으로 묶어야 하는데,
그 영역을 "임계영역"이라고 한다.

  • 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1개)

임계영역은, lock을 얻은 단 하나의 쓰레드만 출입이 가능하다.(자바에서는 객체 1개에 락1개)

 


 

synchronized를 이용한 동기화

  • synchronized로 임계영역(lock이 걸리는 영역)을 설정하는 방법 2가지



메서드 전체를 임계 영역으로 지정하려면, 반환타입 앞에다가 synchronized를 붙이면 된다.
그러면 해당 메서드가 임계 영역이 된다.
그런데, 임계영역은 한번에 한 쓰레드만 사용할 수 있기 때문에, 임계영역은 최소화 해야한다.
임계영역이 많을 수록 성능이 떨어질 것이다.

특정한 영역을 임계 영역으로 지정하려면,  synchronized(객체의 참조변수)를 하고, 문장들을 {}로 묶으면 된다.
그러면 임계 영역이 설정된다.


예를 들면, withdraw라는 출금 메서드가 있는데, synchronized가  반환타입 앞에 붙었으므로, 해당 메서드의 시작부터 끝까지 전부 임계영역이다.
어떤 쓰레드가 출금 작업을 완전히 마치기 전까지는 해당 출금 메서드의 문장들을 실행할 수 없게 되는 것이다.

아니면, 메서드에 붙이는게 아니라 우측처럼  임계영역을 따로 설정을 할 수도 있다.
this는 객체를 가리키는 참조변수로 지정해준 것이고, {}영역이 임계영역으로 설정되는 것이다.

 


 

synchronized를 이용한 동기화 - 예제

예제를 살펴보자.

balance가 잔고인데, 해당 변수는 private로 해야한다.
왜냐하면, priavate이 아니면, 아무리 동기화가 되어 있어도 이 변수에 직접 접근이 가능해서 값을 마음대로 바꿀 수 있기 때문이다.

withdraw는 출금하는 메서드인데, 해당 메서드에 synchronized를 붙여서 동기화처리를 하여 해당 메서드를 임계영역으로 만들었다.
이렇게 동기화 해놓으면, withdraw 출금 메서드는 한번에 한 쓰레드만 실행할 수 있는 것이다.
이 출금 쓰레드는, 출금하려는 돈보다 잔고가 커야지만 출금할 수 있도록 해놓았다.
중간에 sleep은 일부로 결과를 쉽게 보기위해 넣어놓은 것이다. 지금은 신경쓰지 말자.

run()메서드는 랜덤한 금액을 출금하도록 하는 메서드이다.
run()메서드에서 withdraw메서드를 호출하는 것이다.
그리고 잔고를 출력하게 되어있다.

이 예제를, 이렇게 쓰레드 2개를 만들어서 start를 하면 이런식으로 나온다.
잔고가 1000에서 줄어든다.900, 700 이렇게 줄어들다가 음수로 떨어진다.
코드로 봐서는 음수가 나올 수 없다. 왜냐하면, 잔고가 출금하려는 금액보다 클 때만, 뺄셈을 하도록 되어있기 때문이다.
따라서 음수가 절대 나올 수 없는 코드이다.

그런데, 어떻게 음수가 나오냐면,
withdraw를 동기화한 synchronized 코드가 없으면 음수가 나온다.
어떤 a쓰레드가 들어와서 해당 메서드를 실행하다가, catch문쯤에서 자기 차례가 끝나서, 어떤 또다른 B쓰레드가 들어와서 해당문장을 실행하는데,
예를 들어, A라는 쓰레드가 잔고가 충분히 있어서 200원을 출금하려고 한다고 해보자.
if문이 참이되어, if문을 통과해서 catch문 전까지 왔는데, A차례가 끝나서 B쓰레드에게 넘어갔다.
그런데 쓰레드 B는 해당 부분에 왔을 때 잔고가 200원이라서 100원을 출금했다.
그러면 현재 잔고는 200원에서 100원으로 바뀌었다.
그런데 다음에 다시 A쓰레드가 자기 차례가 되어서 보니까 if문은 벌써 통과한 상태라서 잔고검사를 하지 않는다.
무조건 출금한다. 200원을 출금한다.
그러면 잔고는 100원인데 200원을 출금하므로 잔고가 -100원이 된다.
동기화 synchronized를 하지 않으면 이런일이 생긴다.

withdraw메서드를 임계영역으로 지정해놓으면,
A라는 쓰레드가 임계영역 안에 lock을 가지고 들어와서 다른쓰레드가 들어오지 못한다.
이 임계영역에 들어오려면 lock이 있어야하는데, 쓰레드 A가 lock을 가지고 들어와서 B가 들어오지를 못하는 것이다.
그래서 이 쓰레드 A가 다시 돌아와서 이 임계영역을 벗어나면 lock을 다시 반납한다.
그러면 A가 벗어났기 때문에, lock이 해당메서드 입구에 있게되고, 쓰레드 B가 lock을 가지고 그제서야 메서드로 들어올 수 있게 된다.

그래서 이 임계영역 안에는 한번에 1개의 쓰레드만 들어올 수 있게 된다.
이것이 바로 동기화다.

 

그래서 withdraw에 synchronized 키워드를 붙여서 동기화를 하면, 음수가 나오지 않는다.

 

[Ex13_12]

[Ex13_13]

반응형

'JAVA' 카테고리의 다른 글

람다식, 람다식 작성하기  (0) 2022.07.28
wait()과 notify()  (0) 2022.07.27
join(), yield()  (0) 2022.07.07
suspend(), resume(), stop()  (0) 2022.07.07
sleep(), iterrupt()  (0) 2022.07.01