자바는 객체마다 연관된 '모니터'를 갖고 있는데, synchronized 는 이 모니터를 획득/해제하는 방식을 통해서 한 쓰레드만 synchronized 영역을 실행할 수 있도록 함으로써 쓰레드의 동시 접근을 처리합니다.
쓰레드 접근 제어의 단위는 '모니터'인데, 이 모니터는 객체와 관련되어 있으므로, 자바에서 synchronized는 '객체'를 이용해서 접근 제어를 하게 됩니다. 이런 과점에서 1번과 2번은 효과가 (거의) 같다고 볼 수 있습니다. (메서드에 synchronized를 하면, 객체 자신-즉, this와 관련된 모니터를 사용하므로, synchronized(this)를 사용한 코드 블록과 동일한 모니터를 사용하게 됩니다.)
synchronized를 사용하면 아무래도 다중 쓰레드가 코드를 실행할 수 없기 때문에, 각 쓰레드들이 (순서를 알 수 없는 상태로) 순차적으로 synchronized 영역을 실행하게 되죠. 그래서, 한 메서드에서 일부부만 동기화가 필요하다면 이런 경우에는 메서드 전체를 synchronized로 하는 것보단, 특정 코드 영역만 synchronized로 하는 것이 다중 쓰레드 환경에서 쓰레드의 이점을 살릴 수 있게 됩니다.
123456 ... // someCode synchronized(this) { 동기화영역을 최소화할 때에 synchronized 블록을 사용 } ... // anyCode}``` cs
그런데, 멀티쓰레드 환경에 알맞은 코드를 작성하다보면 한 객체의 두 종류의 데이터는 동시에 두 쓰레드가 접근해도 문제가 없는 경우가 있습니다. 그런데, 각 데이터 군은 동시 접근을 막아야 하는 경우가 있죠. 예를 들어, 아래 코드를 보죠. (그냥 설명을 위해 억지로 만든 급조한 예제에요... 더 좋은 예제는 교수님이 만들어주셔요~)
123456789101112131415161718192021222324252627282930 private int width;private int height; private Object sizeLock = new Object(); private int revision; public int getArea() { synchronized(sizeLock) { return width * height; }}public void setSize(int width, int height) { synchronized(sizeLock) { this.width = width; this.height = height; }} public synchronized int getRevision() { return revision;}public synchronized void increaseRevision() { revision++;}}``` cs
여기서, width/height와 revision은 서로 동시 접근을 막을 필요가 없다고 한다고 해 보죠. 이 경우 한 쓰레드가 width/height의 값을 변경하는 동안에 다른 쓰레드가 revision의 값을 접근하는 것을 막을 필요가 없을 것입니다. 따라서, 하나의 모니터(즉, 객체)를 사용하기 보단, 두 데이터 군에 대해 서로 다른 모니터를 사용하도록 함으로써 쓰레드에 대한 동시 접근을 알맞게 제어할 수 있게 됩니다.
그런데, 메서드에 synchronized를 붙일 때에는 조심할 게 있는데, 그것은 바로 this를 사용한다는 점입니다. 아래 코드를 보죠.
12345678910111213141516171819202122232425 final Rectangle rec = new Rectangle(); Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized (rec) { System.out.println("synchronized(rec) begin"); try { Thread.sleep(10000); } catch (InterruptedException e) { } System.out.println("synchronized(rec) end"); } } }); t1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("rec.increaseRevision() before"); rec.increaseRevision(); System.out.println("rec.increaseRevision() after"); }``` cs
이 코드에서 t1 쓰레드는 rec 객체를 이용해서 구성된 synchronized 블록을 실행합니다.
메인 쓰레드는 rec.increaseRevision()을 실행하구요.
여기서 t1 쓰레드의 synchronized 블록과 rec.increaseRevision() 메서드는 동일한 모니터(객체)를 사용하기 때문에 한 쓰레드가 먼저 코드를 실행하게 되면, 다른 쓰레드는 대기하게 됩니다.
실제 실행 결과는 아래와 같아요.
rec.increaseRevision() before ---> 메인 쓰레드는 대기 synchronized(rec) end --> t1 쓰레드가 모니터 해제 rec.increaseRevision() after ---> 메인 쓰레드가 비로서 실행```
이 예만 보더라도, 메서드에 synchronized를 적용하는 것은 다소 위험이 따를 수 있습니다. 따라서, 실제 코드에서는 synchronized를 메서드에 적용하기 보다는 자바 5부터 추가된 Lock을 이용해서 동시 접근을 제어하는 것이 더 안전한 코드를 만들 가능성을 높여준다고 할 수 있습니다.
출처 : http://www.slipp.net/questions/179
댓글 작성자 : beomkyun.choi 님의 댓글 내용입니다 .
다중 쓰레드에서 특정적인 스레드 제어가 필요했는데 정말 고마운 정보 감사합니다 : )
'javaSkills' 카테고리의 다른 글
Hex String -> byte array , byte array -> hex String (0) | 2015.11.05 |
---|---|
자바 int to hex (0) | 2015.11.05 |
윈도우 빌더를 이용한 GUI 구성 TIPS (0) | 2015.11.05 |
자바 1일차에 앞서. (0) | 2015.03.12 |
자바 자습 1일차 (0) | 2015.03.12 |