Google AdSense (text)

hidden logo stop

Moving

거지 같은 이글루스 광고노출 정책이 싫어서,
새 보금자리(http://blog.leocat.kr/)로 이사감.

[Groovy] 동기화는 @Synchronized Computer & Program

Java는 동기화를 위해 메소드에 synchronized 키워드를 사용한다. Java의 동기화는 객체를 기반으로 하고, Groovy도 java를 기반으로 하기 때문에 큰 틀은 다르지 않다. 메소드에 synchronized를 붙였지만 서로 다른 객체는 동시에 그 메소드를 실행할 수 있다는 얘기다. 아주 간단하게 아래의 예를 보자.

class WannaSync {
    static def count = 0
    synchronized void increase() {
        count++
    }
}
def ws1 = new WannaSync()
def ws2 = new WannaSync()

def tf1 = Thread.start {
    100.times {
        sleep 10
        ws1.increase()
    }
}
def tf2 = Thread.start {
    100.times {
        sleep 10
        ws1.increase()
        //ws2.increase() // ws1 대신 ws2를 쓰면 동기화가 되지 않는다.
    }
}

tf1.join()
tf2.join()
println WannaSync.count

200

2개의 thread에서 WannaSync 클래스의 객체를 사용한다. ws1 객체를 공유해서 1씩 증가시키면 동기화된 메소드의 동시 접근을 막기 때문에 출력은 200이 나오게 된다. 하지만 tf2 thead에서 ws1 대신 ws2를 사용하면 출력은 200이 나오지 않는다. 이유는 increase 메소드에 synchronized를 붙였지만, tf1과 tf2 thread는 다른 객체를 사용하기 때문에 동기화가 되지 않게 된다. Java나 Groovy는 메소드가 아닌 객체를 기준으로 동기화가 되기 때문이다. 서로 다른 객체인 ws1과 ws2이 서로 동기화가 되기 위해서는 아래처럼 동기화할 객체를 바꿔줘야 한다.

class WannaSync {
    static def count = 0
    private static final def lockObject = new Object()

    void increase() {
        synchronized(lockObject) {
            count++
        }
    }
}

메소드를 실행하는 this가 아닌 lockObject로 동기화하는 객체를 바꿔준다. (메소드명 앞에 synchronized를 붙여주면 synchronized(this) {...}와 같은 결과가 된다.) 그리고 이 lockObject는 ws1과 ws2에서 함께 사용할 수 있도록 static으로 선언해 준다. 그러면 ws1을 사용하던 ws2를 사용하던 결과는 200이 나올 것이다.


@Synchronized annotation


앞이 길었지만 오늘의 메인 요리는 @Synchronized annotation이다. @Synchronized는 메소드에 붙일 수 있는 annotation이고, 전달인자로는 동기화할 객체를 넣을 수 있다. Groovy doc에 나와 있는 것처럼 메소드의 형태에 따라 살짝 다른 형태의 동기화를 사용할 수 있어 원하는 것을 골라서 사용하면 된다.

class SyncMe {
    private final def lockObject = new Object()
    
    @Synchronized
    void foo() {
        println 'foo'
    }

    @Synchronized('lockObject')
    int bar() {
        return 604
    }

    @Synchronized
    static def sum(a, b) {
        return a + b
    }
}

위와 같은 방법으로 사용할 수 있고, 아래와 같이 동기화 객체 등이 injection된다. (logger에서도 봤지만 개인적으로 groovy의 annotation을 통한 injection은 정말 멋지다~ +ㅅ+ 너무 편해.. 죠아~)

class SyncMe {
    private static final $LOCK = new Object[0]
    private final $lock = new Object[0]
    private final def lockObject = new Object()

    void foo() {
        synchronized($lock) {
            println 'foo'
        }
    }

    int bar() {
        synchronized(lockObject) {
            return 604
        }
    }

    static def sum(a, b) {
        synchronized($LOCK) {
            return a + b
        }
    }
}

1. foo()에 달린 것처럼 아무 전달인자 없이 사용하면 메소드에 붙인 synchronized 키워드처럼 동작한다. 단, 내용을 보면 알듯이 this에 동기화를 하는 것이 아니라 $lock이라는 변수에 동기화한다. 이 변수는 static 변수가 아니기 때문에 각 객체 마다 따로 생성되니 this에 동기화를 한 것과 비슷하다.
2. bar()처럼 전달인자로 변수 이름을 주면 해당 변수에 동기화를 걸게 된다. 변수는 생성해 주어야 한다.
3. static 메소드에 사용하면 객체에만 동기화되는 것이 아니라, 클래스 전체에 동기화를 하게 된다.


- 참고
Groovy Goodness: Synchronized Annotation for Synchronizing Methods
Synchronized - Groovy docs

덧글

댓글 입력 영역

Google AdSense (text/image)