CodeGym /행동 /JAVA 25 SELF /동기화 시 흔한 실수 분석

동기화 시 흔한 실수 분석

JAVA 25 SELF
레벨 52 , 레슨 4
사용 가능

1. 놓친 unlock/release: 부주의함이 부르는 함정

ReentrantLock이나 Semaphore 같은 현대적인 동기화 도구를 사용할 때 가장 교묘한 실수 중 하나는 unlock() 또는 release() 호출을 잊는 것입니다. 락을 해제하지 않으면 다른 스레드는 해제가 될 때까지… 영원히 기다립니다. 프로그램은 멈추고, 왜 아무 일도 일어나지 않는지 화면만 한참 바라보게 됩니다.

ReentrantLock 예제를 보겠습니다:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        // 어이쿠! unlock()을 잊었습니다 — 이제 모두 멈춰 버립니다!
        count++;
    }
}

겉보기에는 아무 문제없어 보이지만, 여러 스레드에서 increment()를 여러 번 호출하면 첫 호출 이후 나머지 스레드는 락이 해제되기를 무기한 기다리게 됩니다.

이 상황을 피하려면 try-finally 구조를 사용하세요:

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

이제 메서드 중간에 예외가 발생하더라도 락은 반드시 해제됩니다.

마치 누군가 화장실에 들어가서(안에서 문을 잠그고) 나갈 때 문을 열지 않고 창문으로 빠져나간 상황과 같습니다. 다른 사람들은 그 사람이 나오기만을 기다리죠… 이렇게 하지 마세요!

2. 잘못된 객체에 동기화: “어, 자물쇠를 엉뚱한 데 걸었네!”

Java에서 키워드 synchronized는 어떤 객체에 대해 접근을 차단합니다. 하지만 잠글 객체를 잘못 선택하면, 동기화는 기대한 대로 동작하지 않습니다.

오류 1: 지역 변수에 대한 동기화

public void doSomething() {
    Object lock = new Object();
    synchronized (lock) {
        // 매번 새 객체 — 동기화가 전혀 되지 않습니다!
        // 스레드들은 서로를 기다리지 않습니다.
        // 임계 구역이 보호되지 않습니다!
    }
}

여기서는 각 스레드가 자신만의 lock 객체를 생성합니다. 그 결과 실질적인 락이 걸리지 않아 스레드들이 동시에 임계 구역에 진입하게 됩니다.

올바른 방법:

private final Object lock = new Object();

public void doSomething() {
    synchronized (lock) {
        // 이제 모든 스레드가 동일한 lock 객체를 사용하므로
        // 서로 실제로 기다리게 됩니다.
    }
}

오류 2: 문자열 리터럴에 대한 동기화

public void doSomething() {
    synchronized ("lock") {
        // 문자열 리터럴은 인터닝됩니다: 프로그램의 다른 부분이
        // 우연히 같은 문자열에 동기화할 수 있습니다!
    }
}

결론:
동기화는 이 목적을 위해 특별히 만든 private 객체에만 하세요. 그리고 그 객체가 다른 어디에서도 사용되지 않도록 하세요.

3. 교착 상태(deadlock): “너 먼저 — 아니, 네가 먼저”, 결국 둘 다 멈춤

데드락(상호 교착)은 고전적인 문제입니다. 두 개(또는 그 이상)의 스레드가 서로 다른 락을 차례로 획득하고 서로를 기다리다가 프로그램이 완전히 멈춰 버립니다.

예시:

public class DeadlockExample {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void method1() {
        synchronized (lockA) {
            // 실험의 명확성을 위해 잠시 기다립니다
            try { Thread.sleep(50); } catch (InterruptedException e) {}
            synchronized (lockB) {
                // ...
            }
        }
    }

    public void method2() {
        synchronized (lockB) {
            try { Thread.sleep(50); } catch (InterruptedException e) {}
            synchronized (lockA) {
                // ...
            }
        }
    }
}

한 스레드가 method1()을, 다른 스레드가 method2()를 호출하면, 첫 번째 스레드는 lockA를 잡고 lockB를 기다리고, 두 번째 스레드는 그 반대가 됩니다. 그 결과 둘 다 영원히 서로를 기다리게 됩니다.

피하는 법:

  • 모든 스레드에서 항상 동일한 순서로 락을 획득하세요.
  • 동시에 보유하는 락의 개수를 최소화하세요.
  • 프로그램이 멈췄다면 진단 도구(예: jstack)를 사용하세요.

비유:
좁은 복도에서 두 사람이 마주쳤는데, 서로 먼저 비켜 주기를 상대에게 요구하는 상황과 같습니다. 결국 둘 다 서서 누가 먼저 포기할지 기다리게 됩니다.

4. 과도한 동기화: “과유불급이 더 낫다?” — 항상 그런 건 아닙니다!

때로는 실수를 피하려고 모든 것을 마구 동기화하는 경우가 있습니다. 그 결과 성능은 떨어지지만, 실질적인 이득은 없습니다.

예시:

public synchronized void add(int value) {
    // 여기에는 동기화가 필요 없는 한 줄만 있습니다!
    System.out.println("추가됨: " + value);
}

이 경우에는 동기화가 필요 없습니다. System.out.println 자체가 이미 스레드 안전하며, 이 메서드는 공유 자원을 다루지 않습니다.

어디서 치명적일까요?
보호가 필요 없는 메서드를 자주 호출하면서 동기화해 버리면, 프로그램의 성능이 급격히 떨어집니다. 실제로는 병렬로 처리할 수 있는데도 스레드들이 줄을 서야 합니다.

모범 사례:
정말 필요한 부분만 동기화하세요. 임계 구역은 가능한 한 작게 유지해야 합니다.

5. volatile의 오사용: “가시성은 있지만, 원자성은 없다!”

Java의 volatile 한정자는 변수의 변경 사항이 모든 스레드에 보이도록 보장합니다. 하지만 이는 원자성은 보장하지 않습니다.

오류:

private volatile int counter = 0;

public void increment() {
    counter++; // 원자적이지 않음!
}

counter++ 연산은 값을 읽고, 증가시키고, 다시 쓰는 단계로 이루어집니다. 두 스레드가 동시에 이 코드를 실행하면 최종 값이 기대보다 작아질 수 있습니다.

올바른 방법:
원자적 연산이 필요하다면 synchronized, AtomicInteger 또는 다른 스레드 안전한 클래스를 사용하세요.

import java.util.concurrent.atomic.AtomicInteger;

private final AtomicInteger counter = new AtomicInteger();

public void increment() {
    counter.incrementAndGet();
}

volatile은 언제 사용할까요?
원자성이 필요 없는 단순한 플래그(예: “작업 종료”)에 적합합니다.

1
설문조사/퀴즈
스레드 동기화, 레벨 52, 레슨 4
사용 불가능
스레드 동기화
스레드 동기화
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION