본문 바로가기
IT/JAVA

Synchronized 잘 알고 사용하기

by 최고영회 2020. 11. 5.
728x90
반응형
SMALL

multi-thread 환경에서 동기화를 제어해야 하는 경우 Synchronized 키워드를 사용한다.

여러 thread 들이 동시에 접근하는 것을 막는다... 라고 간단히 이해하기 보다는 자세히 뜯어볼 필요가 있다.

Thread는 클래스의 멤버변수에 접근할 수 있는데 이 멤버변수는 힙에 올라간다.

즉 여러 Thread가 공유 자원에 접근하는 경우 그리고 그 멤버변수가의 변화가 다른 로직에 영향을 줄 경우

동기화 해줘야 한다. (volatile 이나 Atomic 등을 이용할 수도 있다.)

public synchronized void func(String threadName) {
  // 동기화 필요한 부분 
}

위 코드처럼 method에 synchronized 키워드를 붙이면 이 method 가 포함된 객체에 lock 을 거는것과 같다.

즉 synchronized 키워드를 method 에 붙이면 객체에 lock이 걸리기 때문에 확실하게 동기화로 처리하지만

조금 무식한 방법이기도 하다.

왜? 굳이 lock 걸지 않아도 되는 멤버 변수 등에 대해서도 접근이 불가능해지기 때문이다.

public void func(String threadName) { 
  // 동기화가 필요하지 않은 부분 
  
  synchronized (this) {
    // 동기화 필요한 부분 
  } 
}

위 코드처럼 특정 범위를 synchronized 하면 해당 block 만 동기화 처리 된다.

만약 위 주석 "// 동기화가 필요하지 않은 부분" 에 아무런 코드가 없다면

method 에 synchronized 키워드를 붙인것과 다르지 않다.

synchronized block 의 argument 는 lock 을 거는 대상이다.

위 예제에서는 this 를 사용했는데 this 일 경우 해당 객체 안에 있는 모든 synchronized block에 lock이 걸린다.

즉, 여러 thread 가 각기 다른 synchronized block 에 접근을 시도하더라도

같은 객체(this)의 모든 부분에 lock이 걸리기 때문에 동시처리가 안되고 기다려야 한다.

따라서 lock 이 필요한 멤버변수가 있다면 그 변수에 대해서만 lock 을 걸 수 있도록

별도의 lock 처리 변수를 Object 로 선언하여 처리하는 것이 효율적이다.

아래 예제를 살펴 보자.

private HashMap<String, Integer> map = new HashMap<>(); 
private final Object obj = new Object(); 

.. 

public void put(String key, int val) {
  synchronized(obj) { 
    map.put(key, val); 
  } 
}

물론 위 예제에서 HashMap 이 아닌 HashTable 을 이용한다면 synchronized 가 필요 없다.

HashMap과 HashTable은 사실 똑같으나 내부적으로 동기화를 지원하는가의 차이점만 있다고 보면 된다.

성능이 많이 나오지 않는 제품의 코드를 살펴보고 있는데 불필요한 synchronized 가 남발되고 있다.

주요 자원에 대한 변화로 인해 어떤 프로세스에 영향이 없다면 굳이 동기화 처리 하지 않아도 되며

일반적인 DTO 객체의 경우 setter, getter에 synchronized를 붙일 필요가 없다.

모든 절차에 사용되는 모든 객체의 method 에 synchronized 가 있다면?

불필요한 lock 을 걸게되고 이는 성능에도 좋지 않은 영향을 줄 수 있다.

multi-thread 환경에서 운영된다고 잘 모르겠다고 synchronized 를 남발하면 안된다..

728x90
반응형
LIST