제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java의 synchronized 분석

요즘 jsr 133 자바 메모리 모델을 보고 있는데, 처음에 lock거는 부분에 대해 나오더군요.
자바에서 lock을 거는 것은 보통 synchronized 구문을 이용하는데, 일반적인 자바 책에서는 "synchronized를 걸면 동시 접속이 안된다." 까지만 나와 있었는데, 보다가 보니 재미있는 부분들이 있어서 정리해볼랍니다.

synchronized (anObject){
// code!!
}
위와 같은 구문은 anObject를 기준으로 잡습니다. 그렇기 때문에 완전히 다른 객체끼리도 동기화가 가능합니다. 다음은 제가 주로 사용하는 디버깅 코드입니다.

synchronized (java.lang.Object.class) {
        System.out.println("===========디버깅 시작했다~================");
        System.out.println("time:"
                + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
        System.out.print(new Throwable().getStackTrace()[0].getClassName() + "."
                + new Throwable().getStackTrace()[0].getMethodName() + "()");
        System.out.println("  line: " + new Throwable().getStackTrace()[0].getLineNumber());
        System.out.println(something);
        System.out.println("===========디버깅 끝났다~================");
    }

java.lang.Object는 하나이므로 java.lang.Object를 기준으로 동기화를 시키면 저 코드가 어디 들어가 있더라도 저 코드들끼리 꼬이는 일은 없습니다. 한번 =======디버깅 시작했다.========= 구문이 찍히면, ======== 디버깅 끝났다.===== 구문이 찍히기 전까지는 다른 쓰레드에서 저런 디버깅 메시지를 찍지 않고 기다리게됩니다.

메쏘드에 선언된 synchronized는 자기 객체(this)를 기준으로 동기화합니다.(static 메쏘드의 경우는 자기 클래스를 기준으로!) 그 객체에서 synchronized를 건 모든 메쏘드끼리도 동기화가 됩니다.

다음 코드를 보세요.

package synch.method1;

public class BlackOrWhite {
    private String str;
    private final String BLACK = "black";
    private final String WHITE = "white";
   
    public synchronized void black(){
        str = BLACK;
        try {
            long sleep = (long) (Math.random()*100L);
            Thread.sleep(sleep);
            if (!str.equals(BLACK)) {
                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void white(){
        str = WHITE;
        try {
            long sleep = (long) (Math.random()*100L);
            Thread.sleep(sleep);
            if (!str.equals(WHITE)) {
                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package synch.method1;

import java.util.Iterator;

public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final BlackOrWhite bow = new BlackOrWhite();

        Thread white = new Thread() {
            public void run() {
                while (true) {
                    bow.white();
                }
            }
        };
        Thread black = new Thread() {
            public void run() {
                while (true) {
                    bow.black();
                }
            }
        };
        white.start();
        black.start();
    }
}

두개의 쓰레드가 BlackOrWhite 클래스의 인스턴스인 bow라는 변수를 공유하고 있습니다. 하나의 쓰레드는 black()이란 메쏘드만 주구장창 호출하고, 다른 하나의 쓰레드는 white()만 주구장창 호출해댑니다. 그런데, black()이란 메쏘드와 white()라는 메쏘드가 서로 동기화가 됩니다. 위 코드를 실행시키면 broken!이 찍히는 일은 절대 없습니다. 다시 말해 서로 다른 쓰레드에서 black()과 white()를 호출하지만 두 메쏘드 모두 bow라는 인스턴스에 소속되기 때문에 두 메쏘드가 동시에 진행되는 일은 일어나지 않습니다.

정리를 하자면.
synchronized foo(){
    //메쏘드 내용.
}

foo(){
    synchronized(this){
        //내용
    }
}
은 같은 얘기가 됩니다.

경우에 따라서 method 간에 동기화가 필요 없는 경우도 있을 수 있습니다. 다음의 예를 봅시다.

package synch.two;

import java.util.HashMap;
import java.util.Map;

public class TwoMap {
    private Map<String, String> map1 = new HashMap<String, String>();
    private Map<String, String> map2 = new HashMap<String, String>();
   
    public synchronized void put1(String key, String value){
        map1.put(key, value);
    }
    public synchronized void put2(String key, String value){
        map2.put(key, value);
    }
   
    public synchronized String get1(String key){
        return map1.get(key);
    }
    public synchronized String get2(String key){
        return map2.get(key);
    }
}

위의 예제에서는 두개의 map을 가진 객체가 있습니다. 두 map에 각각 put과 get을 지원합니다. put1()과 get1()은 동기화가 되어야 하지만, put1()과 put2()가 동기화될 필요는 없습니다. 그러나 method에 synchronized가 걸려 있기 때문에 4개의 메쏘드 모두 상호 동기화가 됩니다. 그다지 바람직하지 않은 코드죠.

위의 코드는 아래와 같이 바뀌는 것이 좋습니다.
package synch.two;

import java.util.HashMap;
import java.util.Map;

public class TwoMap {
    private Map<String, String> map1 = new HashMap<String, String>();
    private Map<String, String> map2 = new HashMap<String, String>();
    private final Object syncObj1 = new Object();
    private final Object syncObj2 = new Object();
   
    public void put1(String key, String value){
        synchronized (syncObj1) {
            map1.put(key, value);
        }
    }
    public void put2(String key, String value){       
        synchronized (syncObj2) {
            map2.put(key, value);
        }
    }
  
    public String get1(String key){
        synchronized (syncObj1) {
            return map1.get(key);
        }
    }
    public String get2(String key){
        synchronized (syncObj2) {
            return map2.get(key);
        }
    }
}

이렇게 하면 put1()과 get1() 끼리만, put2()와 get2()끼리만 동기화가 됩니다. 위의 예제에서는 굳이 syncObj1, syncObj2를 만들지 않고, map1과 map2를 이용하여 동기화하여도 됩니다만, 동기화를 위해 저렇게 동기화 전용 멤버 변수를 만들 수도 있다는 것을 보여드릴라고 한 겁니다.

by 삼실청년 | 2008/01/09 15:46 | 컴터질~ | 트랙백(1) | 핑백(4) | 덧글(37)

트랙백 주소 : http://iilii.egloos.com/tb/4071694
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Tracked from Do your best at 2008/03/01 20:54

제목 : Java Synchronized
문서를 처리할 때, 동시에 액세스 하는것을 막기 위해 사용되기도 한다....more

Linked at 건실성실착실 3실 청년! : .. at 2008/06/16 22:56

... 3,4와 같이 읽어들인 String을 풀에 등록시켜서 써야 할 때가 있습니다. 동일한 user에 대해서 어떤 로직에 synch를 걸어야 할 경우가 있죠.(synchronized 분석은 여기서)예를 들어, 한 번만 응모할 수 있는 이벤트가 있다고 칩시다. 일반적으로 디비에 이미 응모한 값이 있는지만 체크하지만 이는 완벽하지 않습니다. 브라우저를 두개 열어놓고 ... more

Linked at 건실성실착실 3실 청년! : .. at 2010/09/09 21:03

... sp;add2. remove3. iterator를 이용하는 부분 전체 이 세 가지를 하나의 sync 객체를 이용해 줘야 합니다.synchronized 분석은 여기!!또 재밌는 부분은 list의 element가 2개만 있을 경웁니다.List&lt;String&gt; list = new ArrayList&lt;St ... more

Linked at Brian's Secret P.. at 2011/02/17 21:04

... 지 않고, map1과 map2를 이용하여 동기화하여도 됩니다만, 동기화를 위해 저렇게 동기화 전용 멤버 변수를 만들 수도 있다는 것을 보여드릴라고 한 겁니다.#http://iilii.egloos.com/4071694 ... more

Linked at 건실성실착실 3실 청년! : .. at 2011/10/28 00:09

... 트죠. 이게 다르면 IllegalMonitorStateException 이 발생합니다. synchronized에 대해서 명확하게 이해하셔야 하며, 잘 모를 때는 요기 클릭!! wait은 반드시 loop 안에서 쓰라고 합니다. 왜냐하면 여러 개의 Thread에서 접근하기 때문에 항상 조건을 따져봐야 하기 때문입니다. 이 내용은 ... more

Commented by A2 at 2008/01/09 22:44
중요 부분에 색을 입혀주셔서 보기가 참 좋네요.
다른 분들도 많은 도움이 될 것 같아요.
Commented by yellowdeve at 2008/01/09 23:42
스레드 관련해서 궁금했었는데...
좋은 정보 감사합니다.
Commented by 용식 at 2008/12/09 18:42
쓰레드 공부하다가 이곳에서 동기화에 대한 내용이 있던 것이 기억나서 보고 갑니다.
감사합니다~
Commented by 삼실청년 at 2008/12/11 17:53
이제 용식님은 친구 같네요.^^
용식님 위에 덧글 다신 분들이 있네요. 첨에 블로그질 시작해서 다른 사람들이 덧글 달았을 때 당황했었습니다. 대답을 해줘야 되는 건지 말아야 되는 건지 뭐라고 대답을 해줘야 되는 건지.. 막 당황했었더랬습니다. 나도 알고 쓴 건지 모르고 쓴 건지 모르겠는 글에 누가 덧글 달아서 화들짝~ 했었습니다.
이게 벌써 거의 1년 전 일이 되었네요.
Commented by 세훈 at 2009/03/11 12:25
마지막 예제에서 put2와 get2메소드가 synchronized되있는데 오기 하신 것인지 아니면 싱크한 이유가 있는것인지 궁금 합니다.
Commented by 삼실청년 at 2009/03/12 21:36
HashSet의 add와는 달리 HashMap의 put은 기존에 있던 값을 override합니다.
따라서 put과 get이 거의 동시에 진행될 경우 put이 먼저 호출되었는데, get이 먼저 작업이 끝나서 갱신되지 않은 값을 가져올 수도 있습니다. 이런 경우 put과 get에 synch를 걸어주게 되면, put이 완료된 후에야 get이 진행되기 때문에 get으로 가져오는 값은 항상 최신값이 됩니다.
질문하신 게 이게 맞나요?
Commented by 세훈 at 2009/03/22 16:27
아뇨.. 마지막예제 보시면 put2메소드와 get2메소드가 두번 동기화 되어있습니다.
this와 syncObj2로요... 아무리 생각해봐도 오기하신것 같아서 말씀드린 겁니다.

덕분에 자바 관련글 재미있게 보고 공부도 많이 하고 있습니다. 감사합니다
Commented by 삼실청년 at 2009/03/22 19:54
아~~ 그러네요.. 오기 맞습니다. 후딱 고쳐놓겠습니다. 메쏘드 선언부에 synchronized가 들어있네요. 지적 감사합니다^^
Commented by 몽백작 at 2010/04/01 08:53
자꾸 헷갈렸는데, 설명 잘 보고 갑니다. 정리를 너무 잘해주셨어요. ^^
Commented by 삼실청년 at 2010/04/09 14:27
우와~ 만우절날 칭찬들었다~
그..그런 거 아니죠??
만우절이라서 그랬던 건 아니죠??ㅜㅜ
Commented by 최자 at 2010/11/18 10:47
글 잘 읽었습니다 도움이 되었습니다
Commented by 삼실청년 at 2010/12/01 02:57
다행입니다^^ 가끔이지만 저도 누군가한테 도움을 주는 존재인 거로군요~ 기쁩니다~~
Commented by 유충근 at 2011/02/08 17:17
잘 읽었습니다. 정말 이해가 쏙 되네요 ... ^^
Commented by 삼실청년 at 2011/02/14 23:52
^^ 도움이 되셨길~~
Commented by 쎄미 at 2011/09/07 10:34
고맙습니다 ^^
Commented by 삼실청년 at 2011/09/26 23:12
별말씀을요^^
Commented by 미스터리 at 2012/01/05 10:27
쉬운설명 감사 드립니다.^^
Commented by 삼실청년 at 2012/02/21 00:21
넹^^
Commented by 하준호 at 2012/01/12 10:41
머리에 쏙쏙 들어오네요~
Commented by 삼실청년 at 2012/02/21 00:23
최대한 쉽게 정리할라고 했었어요. 도움이 되셨다면 다행^^
Commented by 호러블캣 at 2012/02/17 16:53
감사합니다. 정리 너무 깔끔하네요^^
Commented by 삼실청년 at 2012/02/21 00:23
색깔이 깔끔했으면 하는 바램이 있어요.. 저는 색맹도 아닌데 색감이 왜이리 떨어지는지..ㅜㅜ
Commented by 괜한기대 at 2012/06/06 18:06
감사합니다~~~!
쉽게 설명되어있어서 좋아요!ㅋㅋ
Commented by 삼실청년 at 2012/06/16 23:45
^^ 찾아주셔서 감사합니다.
Commented by Xeon at 2012/12/27 19:41
좋은 내용 감사합니다.
Commented by 삼실청년 at 2013/05/06 05:48
매우 오랜 시간이 지났지만 그래도... 답글은 달아야겠다는 일념으로!!!
늦게나마...
도움이 되셨길 바래요...^^
Commented by rtrt at 2013/03/22 23:07
잘보고 갑니다. 많은 도움이되었어요.
Commented by 삼실청년 at 2013/05/06 05:49
진짜 진짜 도움이 되셨으면 좋겠어요..^^
Commented by 여리 at 2013/10/02 16:01
좋은 내용 감사합니다. 그런데 마지막 예제는
private final Object syncObj1 = new Object();
private final Object syncObj2 = new Object()

같은 선언 없이 그냥

public void put1(String key, String value){
synchronized (map1) {
map1.put(key, value);
}
}
public void put2(String key, String value){
synchronized (map2) {
map2.put(key, value);
}
}

public String get1(String key){
synchronized (map1) {
return map1.get(key);
}
}
public String get2(String key){
synchronized (map2) {
return map2.get(key);
}
}
}

하는게 더 깔끔하고 의미도 더 잘 전달 될거 같은데요?
Commented by 삼실청년 at 2013/10/05 04:06
위에서도 적었지만 필요에 따라 저런 식으로 sync 전용 객채를 만들 수도 있다는 것을 보여주기 위해서 쓴 겁니다. 말씀하신대로 그냥 map 객체를 기준으로 sync 잡는 게 더 깔끔하긴 하죠.
Commented by rion110 at 2015/02/24 10:40
첫번째 예제에서 같은 객체안에 있는 메서드끼리 동기화를 보이기 위한것 같은데요.
메서드 자체에서 str값때문에 if문은 원래 안탈것 같은데여.white일때 white로 초기화 되고, black일때는 black으로 초기화 되서 의미가 없는것 같은데요 ...아닌가요?
Commented by 다시 at 2015/11/12 14:48
많이 배우고 갑니다
Commented by nachnine at 2016/01/04 11:29

정리가 매우 잘된 설명 잘 보고 갑니다. 정말 큰 도움이 되었어요!
앞으로도좋은 글 부탁드립니다
감사합니다!
Commented by 쓰레드어렵당 at 2016/03/06 10:04
요즘 짜던 코드에서 헷갈리는 부분이 있었는데
덕분에 해결했네요. 감사합니다!
Commented by 감사맨 at 2016/03/17 15:27
좋은 글이고... 이해 했다고 생각하고 사용했는데... 디테일한 설명이 너무 좋습니다. 감사합니다!. -감사맨-
Commented by DWNC at 2016/09/20 16:00
synchronized 검색하다가 들어왔는데 잘 보고 많이 배우고 갑니다. 잘 설명된 글이다 보니 몇 년이 지나도 꾸준히 들어오는 것 같네요 ㅎ
Commented by 가뭄 at 2016/12/29 13:37
이글에는 문제가 없지만 자칫 오해의 소지가 있어 남깁니다.
락을 거는 객체는 절대로 구문안에서 변경되어서는 안됩니다.

http://egloos.zum.com/Agbird/v/4863601

이글을 읽어보시면 도움이 될 것 같습니다.

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶