본문 바로가기

프로그래밍 언어/Java

동기화 처리 Synchronized

Java의 동기화 Synchronized 필요성 대두

Java에서 MultiThread Programming을 하게 될시, 공유자원에 Thread들이 접근을 하는 경우가 생깁니다.

그럴때, Thread가 공유자원에 접근할때, 동기화를 해줘야 하는 필요성이 있고 그때 사용하는 키워드가 Synchronized입니다.

고유 락과 Synchronized block

  • 자바의 모든 객체는 고유 lock을 가지고 있다.
  • 자바의 Synchronized block은 동시성 문제를 해결하는 가장 간편한 방법으로, 고유 락을 이용하여 여러 스레드의 접근을 제어한다.
public class Counter {
  private int count;

  public int increase() {
    return ++count; // 스레드 안전하지 않은 연산.
  }
}
  • 카운터 클래스는 숫자를 셀때 사용하는 클래스로, increase()를 호출할때마다 count 변수를 1만큼 증가시키고 그 값을 반환한다.
  • ++count문을 실행하였을때, 다음과 같은 3가지의 과정이 병행 된다.
  • 변수를 메모리에서 읽고, 증가시킨 후 다시 메모리에 쓰는것이 그것이다. 이는 동시성 프로그래밍에서 문제가 되는 전형적인 read - modify - write 패턴이다.
  • 두 쓰레드가 동시에 같은 값을 읽고, 값을 증가시켜 저장하면 increase() 호출 횟수로 count값에 차이가 발생한다.
  • 즉 동시성 문제를 해결하기 위해 count변수에 접근하는 스레드를 제어해야 한다.
public class Counter {
  private Object lock = new Object();
  private int count;

  public int increase() {
    synchronized(lock) {
      return ++count;
    }
  }
}
  • lock이라는 Object 인스턴스를 이용하여 여러 스레드가 동시에 count 변수에 접근하지 못하도록 제어했다.
  • 이제 increase() 메소드는 한번에 한 스레드만 실행이 가능하다. 
  • 한 스레드가 먼저 락을 획득한 경우에 다른 스레드는 기다려야 한다.
  • 다른 방법으로는 별도의 객체 생성없이 Counter 인스턴스도 자바 객체이므로 락으로 사용할수 있다.
public class Counter {
    private int count;

    public int increase() {
        synchronized(this) {
            return ++count;
        }
    }
}
  • this를 이용하여 별도의 락 생성 없이 synchronized 블록을 구현하였다.
  • 예제의 synchronized 블록은 increase() 메서드 전체를 감싸고 있는데, 이런 경우에는 메서드에 synchronized 키워드를 붙여주는 것으로 대신할 수 있다.
public class Counter {
  private int count;

  public synchronized int increase() {
    return ++count;
  }
}

Synchronized Method vs Synchronized Block

  • Synchronized Method는 객체 자신에 lock을 거는것이다. 즉 다른 서로 Synchronized 키워드가 붙은 메소드에 접근할때도 객체 자신에 락을 거는 것으므로 접근이 불가능하다.
  • Synchronized Block은 동시에 락이 걸려야 하는 부분을 정할수가 있다. 다음의 예제를 보고 말하자면,
private HashMap<String, String> mMap1 = new HashMap<>();
private HashMap<String, String> mMap2 = new HashMap<>();

private final Object object1 = new Object();
private final Object object2 = new Object();

public void put1(String key, String value) {
    synchronized(object1) {
        mMap1.put(key, value);
    }
}

public String get1(String key) {
    synchronized(object1) {
        return mMap1.get(key);
    }
}

public void put2(String key, String value) {
    synchronized(object2) {
        mMap2.put(key, value);
    }
}

public String get2(String key) {
    synchronized(object2) {
        return mMap2.get(key);
    }
}
  • put1()과 get1()이 동시에 lock이 걸려야 하고, put2()와 get2()가 동시에 lock이 걸려야 한다면, 다음과 같이 객체를 생성해 서로 다른 객체에 lock을 걸어줌으로써, 동기화 처리를 진행할수 있다.

결론

  • Synchronized Method는 동시성이 확실히 보장이 되지만, 객체 자신에게 lock을 걸기때문에 여러 Synchronized Method를 가질 경우, 쓰레드 병목 현상이 발생되어 Single Thread로 돌아가는 상황과 마찬가지 상황에 직면할수 있습니다.
  • 그렇기에, Synchronized. block을 사용하여, 서로 Lock이 걸려야 하는 Method들끼리 묶어, 병목 현상을 해결하여 MultiThread 환경에서의 퍼포먼스를 강화시킬수 있습니다.

 

'프로그래밍 언어 > Java' 카테고리의 다른 글

자바 Transactional  (0) 2022.07.10
Interface가 왜 필요할까?  (0) 2022.07.09
Stream 확실히 알기  (0) 2022.06.12
Stream vs List  (0) 2022.06.12
제네릭(Generic)  (0) 2022.06.08