자바는 객체마다 연관된 '모니터'를 갖고 있는데, synchronized 는 이 모니터를 획득/해제하는 방식을 통해서 한 쓰레드만 synchronized 영역을 실행할 수 있도록 함으로써 쓰레드의 동시 접근을 처리합니다.

쓰레드 접근 제어의 단위는 '모니터'인데, 이 모니터는 객체와 관련되어 있으므로, 자바에서 synchronized는 '객체'를 이용해서 접근 제어를 하게 됩니다. 이런 과점에서 1번과 2번은 효과가 (거의) 같다고 볼 수 있습니다. (메서드에 synchronized를 하면, 객체 자신-즉, this와 관련된 모니터를 사용하므로, synchronized(this)를 사용한 코드 블록과 동일한 모니터를 사용하게 됩니다.)

synchronized를 사용하면 아무래도 다중 쓰레드가 코드를 실행할 수 없기 때문에, 각 쓰레드들이 (순서를 알 수 없는 상태로) 순차적으로 synchronized 영역을 실행하게 되죠. 그래서, 한 메서드에서 일부부만 동기화가 필요하다면 이런 경우에는 메서드 전체를 synchronized로 하는 것보단, 특정 코드 영역만 synchronized로 하는 것이 다중 쓰레드 환경에서 쓰레드의 이점을 살릴 수 있게 됩니다.

1
2
3
4
5
6
    ... // someCode
    synchronized(this) {
        동기화영역을 최소화할 때에 synchronized 블록을 사용
    }
    ... // anyCode
}```
cs
그런데, 멀티쓰레드 환경에 알맞은 코드를 작성하다보면 한 객체의 두 종류의 데이터는 동시에 두 쓰레드가 접근해도 문제가 없는 경우가 있습니다. 그런데, 각 데이터 군은 동시 접근을 막아야 하는 경우가 있죠. 예를 들어, 아래 코드를 보죠. (그냥 설명을 위해 억지로 만든 급조한 예제에요... 더 좋은 예제는 교수님이 만들어주셔요~)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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를 사용한다는 점입니다. 아래 코드를 보죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        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

+ Recent posts