before 2020/JAVA

자바 쓰레드에 대한 모든것!!..은 아니고;;(JAVA Thread)

hom2s 2009. 6. 27. 16:10
※원래 쓰레드의 개념과 사용법에 대해 처음 공부하시는 분들을 위해 굉장히 상세히 포스팅 할 예정이었으나 역시 단순한 포스트에 모든 내용을 실을 수 있을 정도로 세상은 만만치 않았습니다.ㅡㅡ;;; 그림 한장 없이 텍스트로만 , 그것도 상세히가 아닌 띄엄띄엄 포스팅하게 되어서 안타깝습니다. 너무 판을 벌려서 정리가 안되는군요;;;;

1. 쓰레드의 의미
쓰레드(Thread)란 무엇인가?
아직 쓰레드 프로그래밍을 해보지 않은 사람이라면 가끔 이런 생각을 해봤을 것이다. '프로그램을 만들 때 하나의 프로그램에서 동시에 두가지 이상의 작업이 이루어지게 하려면 어떻게 해야할까?'

예를들어 "음악이 흐르는 포토뷰어" 라는 프로그램을 생각해보자. 사진을 감상하는 프로그램인데 사진만 감상하긴 좀 밋밋해서 배경으로 음악을 깔고 싶어졌다. 사진을 보면서 음악을 듣는 것에 그치는게 아니라 사진을 넘기고 음악을 바꾸는 작업이 동시에 이루어져야 한다면?

분명 지금까지의 만들어본 프로그램은 main 함수라는 하나의 흐름에 의해 모든것이 이루어 졌을 것이다. 하지만 이젠 사진도 흘러가야하고 음악도 흘러가야 하게 된 것이다. 이렇게 하나의 프로그램 안에 둘 이상의 흐름을 가진 프로그램을 만들 때 우리는 쓰레드를 이용한다.

2. 쓰레드와 멀티프로세스의 차이점
둘 다 두가지 이상의 작업을 동시에 처리할 수 있는 매커니즘이다. 하지만 분명 차이가 있다.

우선 프로세스(process)에는 프로세스를 이루는 몇가지 영역이 있는데 보통 Code영역,Data영역,Heap영역,Stack영역으로 구성된다. 이것들이 모여 하나의 프로세스가 된다. 멀티프로세스가 이 같은 프로세스를 통째로 여러개 수행시키는 것이라 한다면 쓰레드는 하나의 프로세스 옆에 붙어 기생(ㅡㅡ;) 하며 수행되는 놈 쯤이 되겠다.

쓰레드는 프로세스가 가진 Code,Data,Heap 영역을 프로세스와 공유한다. 오직 Stack만 따로 가지고 있는 놈을 쓰레드라고 부른다. 아무래도 공유하는 영역을 가지다보니 하나의 프로세스에 붙어있는 여러 쓰레드간, 혹은 프로세스와의 통신에 별다른 부담이 없다. 멀티프로세스에서는 프로세스간에 통신을 하기 위해서 IPC를 통해서 통신이 가능하다. 그리고 프로세스를 통째로 여러개 돌리면 쓰레드보다 시스템의 부하가 많이 걸릴것이다.

여튼 우리는 쓰레드가 프로세스의 Stack영역을 제외한 나머지 영역을 공유한다는 사실 정도만 알고가자.

3. Lock과 Monitor에 대하여
쓰레드는 위에서 말했듯이 동시에 2가지 이상의 흐름을 갖는 것을 의미한다. 하지만 문제가 되는경우가 몇가지 있다. 그중 하나가 자원공유에 관한 것이다.

만약 A라는 쓰레드가 money라는 변수의 값을 변경한다고 하자. money가 100원이라는 값을 가지고 있는데 A가 여기에 50원이라는 값을 넣으려고 한다. 우선 'money + 50원'을 해서 150원 이라는 값을 얻었다. 이제 money에 150원이라는 값을 대입하기만 하면 된다. 근데 그때 B라는 쓰레드가 money에 100원을 더 넣은 것이다. B는 200원이 되었을 거라 생각했다. 물론 200원이 되었다. 근데 그 바로 직후에 A쓰레드가 150원이라는 값을 money에 대입한것이다. A는 당연히 150원이 있는걸로 알고 있지만 B는 분명 200원이어야 할 money가 150원으로 둔갑한 것에 대해 기겁하게 되는 것이다.

그렇다. 두개의 쓰레드가 동시에 도는것은 좋은데 하나의 자원(money)을 무분별하게 사용하다보니 이런일이 벌어진 것이다.
Lock과 Monitor란 이런 문제를 해결하기 위해 등장한 것이다. Monitor는 하나의 쓰레드가 하나의 객체에 대한 락을 획득했을 때 다른 쓰레드가 그 객체에 접근하여 동기화 시킨 영역에 접근하는 행위를 막아준다.

일단 이렇게 일이 처리되면 위의 예에서 A가 먼저 money를 사용중이기에 B는 money에 100원을 추가할 수가 없다. A가 작업이 끝나고 150원이 된 후에 B가 100원을 추가해 결국 250원을 만들 수 있는것이다.

이런 Lock을 mutual exclusion lock, 뮤텍스락이라고도 부르며 한국말로는 상호배제락이라 부른다. 그리고 이러한 락에 의해 쓰레드와 공유자원사이의 상호작용을 제어하는 행위를 동기화(synchronization)라고 부른다.

4. Monitor
Java의 오브젝트에는 Monitor가 할당된다. 이 모니터는 자신을 소유한 쓰레드를 제외한 다른 쓰레드의 접근을 막는다. 이러한 특성을  mutual exclusion 혹은 뮤텍스라고 부른다. 이렇게 모니터는 일시적으로 다른 쓰레드가 외부에서의 접근을 포기하게 만드는 기능을 하는데 만약 쓰레드가 어떤 오브젝트의 락을 획득하면 그 락을 획득한 동기화 영역에는 다른 쓰레드의 접근이 불가하다. 다른 쓰레드가 이 오프젝트의 락을 획득하기 위해서는 이전 쓰레드가 잡고있는 락을 풀어줘야지만 락의 획득이 가능하다. 이는 wait같은 대기모드 함수를 호출하거나 동기화 블록이 끝나는 부분에서 락을 자동으로 해제하게 된다.
< JAVA Whitre Paper 문서에서 발췌 - The most basic of these methods is synchronization, which is implemented using monitors. Each object in Java is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor. The synchronized statement computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed.>

5. 데드락
여러 회사의 면접 질문으로 굉장히 많이 나오는 데드락.
A라는 쓰레드가 특정 객체의 락을 얻게되면 그에 따른 자원을 block 하게 된다. 근데 그 쓰레드가 자신의 작업을 처리하기 위해 외부의 자원이 필요로 하는 경우가 생기게 된다. 다행이 그 자원이 누구나 접근 가능한 상태라면 불러와서 사용할 수 있지만 만약 B라는 쓰레드가 그 자원에 락을 걸고 있다면 A쓰레드는 B가 락을 해제 할 때 까지 기다리는 수밖에 없을 것이다. 설상가상으로 B 또한 A가 block하고 있는 자원을 원하고 있다면 A와 B는 서로가 Lock을 풀때까지 무한정 기다리게 되는데 이러한 상태를 데드락(Dead Lock)이라고 부른다.
이 데드락을 해결하는 방법은 데드락이 발생하기 않게 프로그래밍 하는 방법이 가장 좋다고 밖에 할 수 없다. 그외에 쓰레드의 우선순위에 따라 하나의 쓰레드를 포기하는 방법이 있는데 이러한 해결책은 프로그램의 성능저하의 큰 원인이 될것이다.
데드락이 발생하는 코드를 짜보는 것도 좋을듯...(왜!!;;)

6. 동기화(synchronized)
- 함수 이름 앞에 쓰이는 synchronized는 synchronized(this)와 동일함
- synchronized(instance) 와 synchronized(Class)와의 차이
   => http://pupustory.tistory.com/149
- 블록으로 동기화를 사용할 때 synchronized()의 인자로 들어가는 객체는 그 객체를 Lock에 의해 보호하겠다는 말씀
- synchronized의 블록의 의미는 그 블록의 코드가 끝날때 까지 인자로 받은 객체의 Lock을 풀지 않겠다는 의미
- A라는 쓰레드가 Lock을 건 객체를 B라는 쓰레드가 그 객체의 동기화 블록외의 코드를 접근 가능하다. 즉 Lock을 건다는 말은 그 객체의 전체에 락을 거는것이 아니라 동기화 영역에 대해서만 락이 걸린다는 의미
- 꼭 Lock이 걸려있는 객체가 어떤일을 해야만 하는것은 아니다. 마치 동기화를 위한 변수처럼 사용이 가능하다.
   => http://iilii.egloos.com/4071694

7. wait/notify (작업순서)
하나의 synchronized된 자원을 두개이상의 쓰레드가 사용할 때는 하나의 쓰레드가 모든 작업이 끝난 이후에 다음 쓰레드가 작업을 시작하는 것이 아니라 때론 블록된 단위별로 번갈아 가면서 사용해야 할경우가 있다. 이럴때 현재의 쓰레드의 락을 잠깐 해제하고 대기 중이던 쓰레드를 깨워서 사용하는 과정이 필요한데 이러한 과정을 가능하게 하는 함수가 wait() 과 notify() 함수이다.

- wait()에 걸리면 그 코드를 수행하고 있던 쓰레드를 휴지상태(sleep)상태로 만들어줌. 그자리에서.
- wait()과 동시에 쓰레드가 대기상태로 돌아가면서 인자로 받은 객체에 걸고있던 Lock을 해제하게 됨
- 다른 쓰레드가 위의 객체에 Lock을 가지게 되고 작업이 끝나면서 notify()호출해서 휴지상태로 대기하고 있던 쓰레드를 깨워줌
- 단, notify()가 호출된다고 바로 대기 쓰레드가 시작되는게 아니라 지금 수행되던 쓰레드의 synchronized 블록이 끝나야지 대기중이던 쓰레드가 실행된다.
- 대기 쓰레드가 실행될 때는 그 쓰레드가 다시 객체에 대한 Lock을 가지며 wait() 코드 다음 코드부터 시작된다.

그밖에 쓰레드의 상태를 관리하는 함수들이 있다. 그 쓰레드의 상태와 이를 관리하는 함수는 다음의 링크에서 확인 할 수 있다.
=> http://www.javastudy.co.kr/docs/jm/jm15.html
=> http://www.javastudy.co.kr/docs/lec_java/thread/t2.html

8. 쓰레드 사용방법
- 아.. 결국 포스팅이 귀찮아져 이렇게 긁어 오기를 해버리는구나.. ;;;;
   => http://signpen.net/blog/106
   => http://blog.daum.net/bifrost0076/5882414

add) 세마포어
.. A사의 2차 면접에 나왔던 질문. OS질문 하더니 갑자기 세마포어에 대해 묻더라.. 모니터와 유사한 개념이지만 조금은 다르다. 카운터를 설정할 수 있는 세마포어도 있고 .. 조금 다르긴한데 시간을 두고 포스팅하자.. 쓰레드 포스팅을 정리하는데 너무 많은 시간과 노력을 투자해버려서 이젠 보기도 싫다.





'before 2020 > JAVA' 카테고리의 다른 글

Java의 Call By Value 와 Call By Reference  (13) 2014.05.14