Skip to content

Latest commit

 

History

History
150 lines (114 loc) · 11.5 KB

File metadata and controls

150 lines (114 loc) · 11.5 KB

Synchronized

자바 Synchronzied

  • 자바에서 스레드 간의 동기화를 달성하기 위해 사용
  • 메서드 레벨 혹은 정적 메서드 레벨, 블록레벨으로 동작하며 한번에 하나의 스레드만 모니터를 얻어 동기화
    • 메서드 레벨 : 메서드 전체가 하나의 임계 구역이 되며, 이 메서드를 호출하는 모든 스레드는 해당 객체의 모니터 락을 획득해야 메서드에 진입할 수 있음
    • 정적 메서드 레벨 : 정적 메서드 전체가 하나의 임계 구역이 되며, 이 메서드를 호출하는 모든 스레드는 해당 클래스의 클래스 락을 획득해야 메서드에 진입할 수 있음
    • 블록 레벨 : 특정 코드 블록이 하나의 임계 구역이 되며, 이 블록을 실행하는 모든 스레드는 주어진 객체(lock)의 모니터 락을 획득해야 블록에 진입할 수 있음 (this를 통한 해당 객체도 가능)

public synchronized void synchronizedMethod() {
    // 동기화된 메서드 본문
}

public static synchronized void synchronizedStaticMethod() {
    // 정적 메서드에 붙는 경우
}

public void synchronizedBlockMethod() {
    synchronized (lockObject) { // lockObject의 모니터를 잠그는 동기화 블록. 해당 블록만 동기화를 진행
        ...
    }

    ... // 동기화가 적용되지 않는 메서드 본문
}

단일 연산

  • 여러 단계로 구성된 작업이 모두 성공적으로 완료되거나 실패해야 하는 연산을 의미
  • 해당 작업이 중간에 중단되거나 다른 스레드에 의해 중간에 변경되지 않는 것을 보장하므로 여러 스레드가 동시에 같은 작업에 접근할 때 스레드 안전해지게 함

스레드 한정(Thread Confinement)

  • 다중 스레드 환경에서 데이터나 객체를 특정 스레드에만 제한하여 접근하도록 하는 디자인 패턴이나 기법

모니터 패턴(Monitor Pattern)

  • 모니터 패턴은 다중 스레드 환경에서 동기화를 위한 디자인 패턴 중 하나
  • 이 패턴은 상호 배제(mutual exclusion)와 조건 변수(condition variable)를 사용하여 공유 자원에 대한 안전한 접근을 보장
    • 조건 변수란 스레드가 특정 조건을 충족할 때까지 기다리도록 하는 메커니즘
  • 자바 모니터 패턴의 핵심 메서드들은 synchronized 키워드와 wait(), notify(), notifyAll() 메서드

synchronized

  • 객체에 대한 참조를 계산하고 해당 개체의 모니터에서 잠금 작업을 수행하려고 시도하고 잠금 작업이 성공적으로 완료될 때까지 더 이상 진행하지 않음 ( 모니터를 얻을 때까지 차단됨 )
  • 잠금 작업이 수행된 후 문의 본문이 synchronized 실행 후 정상적으로 또는 갑자기 본문 실행이 완료되면 동일한 모니터에서 잠금 해제 작업이 자동으로 수행됨
  • 스레드 t는 특정 모니터를 여러 번 잠글 수 있고 각 잠금 해제는 한 번의 잠금 작업 효과를 반전함
    • 모니터의 잠금 횟수가 증가하는 것을 해제할 때 반전시킨다는 의미로 모니터를 해제할 때마다 잠금 횟수가 1씩 감소하고, 잠금 횟수가 0이 되면 해당 모니터는 완전히 해제되어 다른 스레드가 해당 모니터를 사용할 수 있게함
  • synchronized 메서드의 경우는 메소드가 인스턴스 메소드인 경우 해당 메소드가 호출된 인스턴스(즉, this메소드 본문 실행 중에 알려지는 객체)와 연관된 모니터를 잠금, 메서드가 정적인 경우 메서드가 정의된 클래스를 나타내는 Class 개체와 연관된 모니터를 잠금. 메서드 본문의 실행이 정상적으로 또는 갑자기 완료되면 동일한 모니터에서 잠금 해제 작업이 자동으로 수행함

효율적인 코드 작성 측면에서, Synchronized는 좋은 키워드인지?

  • 스레드 안전성을 보장하는 도구이지만 동기화된 메서드나 블록에 여러 스레드가 접근하지 못해 스레드들은 대기 상태로 들어가 성능을 저하시킬 수 있음
  • 여러 락을 순서 없이 획득하는 등의 부적절한 동기화 사용은 데드락을 초래할 수 있음
  • 불필요하게 넓은 범위에 동기화를 적용하면 락 경합이 심해져 성능이 저하될 수 있음

wait set(대기 집합)

  • 연관된 모니터를 갖는 것 외에도 모든 객체에는 연관된 wait set이 있고 자바에서 객체의 대기 집합은 해당 객체에 대해 대기 중인 스레드들의 집합
  • 대기 집합(wait set)은 각 객체마다 별도로 존재하며, 해당 객체에 대해 대기 중인 스레드들의 집합을 나타냅니다. 이 대기 집합은 객체가 생성될 때 빈 상태로 시작
  • 스레드가 Object 클래스의 wait() 메서드를 호출하여 대기 상태에 들어갈 때, 해당 스레드는 대기하는 스레드들의 집합인 wait set에 추가됨
  • 대기 집합에 스레드를 추가하거나 제거하는 기본 동작은 원자적이고 대기 집합은 오로지 Object.wait(), Object.notify(), Object.notifyAll() 메서드를 통해 조작함
    • 원자적(atomic) : 한 번에 완전히 실행되거나 실행되지 않는 작업을 나타냄. 작업이 분할되거나 중간에 중단되지 않고 전체가 한꺼번에 실행됨을 의미
  • 또한 대기 집합의 조작은 스레드의 인터럽트 상태(interruption status) 및 인터럽트와 관련된 Thread 클래스의 메서드에 의해 영향을 받을 수 있다고 함
    • sleep() 메서드는 스레드를 일시정지시키고 다른 스레드의 알림이나 신호를 기다리지 않고 실행되므로 대기 집합에 추가되지 않아 영향을 끼치지 않음. 즉 해당 잠든 스레드는 여전히 모니터를 가지고 있음
    • join() 메서드는 다른 스레드가 종료될 때까지 대기하고, 그 스레드가 종료되면 실행을 재개하므로 대기 집합(wait set)에 스레드가 추가됨
    • sleep(), join()는 Thread 객체의 메서드이고 이후 설명하는 wait(), notify() 등의 메서드는 Object의 메서드
  • 즉, 요약하자면 객체의 스레드는 모니터를 얻지 못했다면 대기 집합에 추가 되어 모니터를 해제하는 알림(Notification)을 기다기다가 해당 알림을 통해 대기되어 있던 스레드가 모니터를 획득할 수 있으며 해당 방식으로 동기화가 이루어진다.
  • 대기 집합에 들어가는 스레드는 스레드 생명주기의 대기 상태가 되는 것

public class MyClass {
    public synchronized void synchronizedMethod() {
        // synchronizedMethod 내용
    }
}
// 인스턴스 메서드에서 사용하면 MyClass 객체의 대기 집합에 들어감

public class MyClass {
    private final Object lock = new Object();

    public void someMethod() {
        synchronized (lock) {
            // 동기화가 필요한 코드
        }
    }
}

// someMethod() 안의 블록이 있으며 lock 객체의 대기 집합에 들어감

자바 모니터 동기화를 위한 메서드

wait()

  • 다른 스레드가 동일한 객체에 대해 notify() 또는 notifyAll()을 호출할 때까지 현재 스레드가 강제로 무기한 대기

notify(), notifyAll()

  • 특정 객체의 모니터에 대한 액세스를 기다리고 있는 스레드를 깨우기 위해 notify(), notifyAll() 메서드를 사용
  • notify() : 이 객체의 모니터에서 대기 중인 모든 스레드에 대해, notify() 메서드는 임의로 하나를 깨웁니다. 깨울 스레드를 정확히 선택하는 것은 비결정적이며, 구현에 따라 달라짐
  • notifyAll() : 이 객체의 모니터에서 대기 중인 모든 스레드를 깨우고 깨어난 스레드들은 이 객체를 동기화하려는 다른 스레드와 마찬가지로 일반적인 방식으로 경쟁함
  • 차이점은 notify()는 임의의 스레드 하나만을 깨우고 그 외 대기 상태의 스레드들은 그대로 대기하고 있음, notifyAll()은 대기중인 스레드를 모두 깨우고 모니터를 얻기 위해 경쟁하고 한 스레드가 모니터를 얻으면 나머지 스레드들은 대기 상태가 됨

스레드대기상태


Synchronized 를 대체할 수 있는 자바의 다른 동기화 기법

  • ReentrantLock : synchronized 블록을 대체할 수 있는 가장 일반적인 락 클래스. 재진입 가능한 락을 제공하여 같은 스레드가 여러 번 락을 획득할 수 있음
  • StampedLock : ReadWriteLock의 개선된 버전으로, 낙관적 읽기(optimistic read)를 지원하여 읽기 성능을 향상시킬 수 있음
  • Atomic 클래스 : 아토믹 패키지의 원자 클래스는 기본 데이터 타입의 원자적 연산을 제공. 이 클래스들은 CAS(Compare-And-Swap) 알고리즘을 사용하여 락 없이 스레드 안전한 연산을 수행할 수 있음
  • Concurrent Collections : 자바의 java.util.concurrent 패키지는 스레드 안전한 컬렉션을 제공. 이러한 컬렉션들은 내부적으로 동기화되어 있어, 별도의 락을 사용하지 않고도 스레드 안전한 작업을 수행 가능
  • Semaphore : 하나 이상의 스레드가 공유 자원에 접근할 수 있는 허용 수를 제어
  • CyclicBarrier : 정해진 수의 스레드가 모두 도착할 때까지 기다렸다가, 모든 스레드가 동시에 실행을 재개할 수 있게 하는 동기화 보조 클래스
  • CountDownLatch : 하나 이상의 스레드가 다른 스레드의 작업이 완료될 때까지 대기하도록 할 수 있는 동기화 보조 클래스
  • volatile 키워드 : 변수의 값을 각 스레드가 항상 최신 값으로 읽도록 보장함. 변수에 대한 읽기/쓰기 작업이 원자적이지 않다면 volatile만으로는 동기화가 충분하지 않음
  • 분산 락(distributed lock) : 모니터 락이 아닌 외부 시스템에서 제공되는 락. 분산 락은 여러 프로세스나 여러 서버에서 동일한 자원에 접근할 때 동시성 제어를 위해 사용됨
    • Redis, ZooKeeper, Etcd

ThreadLocal이란?

  • 자바에서 각 스레드마다 독립적인 변수의 복사본을 제공하는 메커니즘
  • ThreadLocal은 스레드 로컬 변수를 생성하고 관리하는 데 사용되며, 이를 통해 각 스레드는 다른 스레드와 독립적으로 자신의 변수를 유지할 수 있음
    • ThreadLocal 변수는 각 스레드마다 독립적으로 할당되며, 다른 스레드와 공유되지 않음
  • 여러 스레드가 동일한 자원을 사용하는 경우, 스레드 안전성을 보장하기 위해 synchronized나 락을 사용할 필요 없이, 각 스레드마다 독립된 자원을 할당할 수 있음
  • 스레드마다 고유한 컨텍스트 정보를 유지해야 하는 경우 유용합니다. 예를 들어, 트랜잭션 관리, 사용자 세션 정보, 로깅 컨텍스트 등이 있음
  • ThreadLocal 변수는 각 스레드에 대해 독립적으로 저장되므로, 스레드가 종료되더라도 ThreadLocal에 저장된 값이 제거되지 않으면 메모리 누수가 발생할 수 있음
  • ThreadLocal을 사용할 때 스레드 생명 주기 동안 해당 변수에 접근할 수 있지만, 특정 상황에서는 ThreadLocal의 사용이 계층 구조를 벗어나 문제를 일으킬 수 있음

Reference