자바에서 모니터가 동작하는 원리 (with synchronized)

2023. 12. 28. 22:37·개발/Java
반응형

모니터란

모니터란 뮤텍스나 세마포어보다 더 고수준의 동기화 기법이다.

전공 수업에서는 세마포어를 편리하게 사용하기 위해 인터페이스를 제공한 것이 모니터라고 배웠었다.

부족하지만 수업을 듣고 정리해 놓은 글이 있다.

 

https://devdebin.tistory.com/16#%EC%84%B8%EB%A7%88%ED%8F%AC%EC%96%B4-1

 

임계 구역과 임계 구역 해결 방법

2021. 10. 2. 00:51 2022. 6. 22. 15:30 복습을 위한 수정 시작 프로세스는 독립적으로 작업을 할 수도 있고 공유된 자원을 가지고 공동 작업을 할 수도 있다고 이전 장에서 언급했다. 이번에는 본격적으로

devdebin.tistory.com

 

자바에서의 모니터

  • 모든 자바 객체는 모니터를 가진다.
  • 여러 스레드가 객체의 임계 영역에 진입하려고 할 때 JVM은 모니터를 사용해 스레드 간 동기화를 제공한다.
  • 자바의 모니터는 상호 배제 및 협력이라는 두 가지 동기화 기능을 제공하고 있으며 이를 위해 뮤텍스와 조건 변수를 사용한다.

 

상호 배제(Mutual Exclusion)

  • 객체가 가지고 있는 모니터 Lock을 통해 여러 스레드가 동시에 공유 자원에 접근하는 것을 막아 데이터의 일관성과 안전성을 보장하는 메커니즘
  • JVM은 synchronized 키워드를 사용해 뮤텍스 동기화를 암묵적으로 처리해주고 있으며 synchronized는 메서드나 코드 블록에 적용 가능
  • synchronized 블록은 해당 객체의 모니터를 획득할 수 있으며 모니터를 획득한 스레드만이 임계영역에 접근 가능하고 그 외 다른 스레드들은 차단되어 대기 상태가 된다.
  • synchronized 블록을 빠져 나오면 모니터 Lock이 해제되고 대기 중인 다른 스레드 중 하나가 락을 얻고 임계 영역에 진입하여 작업을 수행하는 식으로 상호배제가 보장된다.

 

협력(Cooperation)

  • 협력은 모니터의 Condition Variable (조건변수)를 통해 스레드 간 공동의 목표를 위해 상호협력으로 데이터의 일관성과 안전성을 보장하는 동기화 메커니즘이다.
  • 조건변수는 Object 클래스의 메서드인 wait(), notify(), notifyAll()과 함께 작동하며 특정 조건이 만족될 때까지 스레드를 대기시키는 기능을 제공한다.
  • 스레드가 특정 조건에 부합하지 않으면  wait() 메서드를 호출해 조건 변수의 대기 셋에 들어가 대기한다.
  • 다른 스레드가 특정 조건을 만족해서 notify() 또는 notifyAll() 메서드를 호출해 해당 조건 변수의 대기셋으로부터 스레드들을 깨워 실행시키게 된다.
  • 조건 변수를 통해 스레드 간 대기와 통지를 서로 조절하면서 경쟁 조건과 같은 문제를 방지할 수 있다.

보통 모니터는 여러개의 조건 변수를 가지지만 자바의 모니터에는 오직 한 개의 조건 변수만 가질 수 있다.

 

모니터 대기 구조

자바의 모니터 내부에는 EntrySet(진입셋)과 WaitSet(대기셋) 이라는 대기 자료 구조가 있다.

이들은 멀티 스레드 환경에서 스레드들 간의 상호 작용을 조절하는 데 사용한다.

 

EntrySet

  • EntrySet은 모니터의 Lock을 획득하기 위해 대기 중인 스레드를 모아 놓은 자료구조다.
  • 스레드가 Lock을 사용 중인 경우 그외 다른 스레드는 EntrySet에 들어간다.
  • EntrySet에 있는 스레드들은 Lock이 반납될때까지 기다리며 락이 반납되면 Entry Set 중 하나의 스레드가 락을 획득하고 임계 영역으로 진입하게 된다.

 

WaitSet

  • WaitSet은 모니터의 조건 변수와 함께 사용하는 자료구조이며 스레드들이 특정한 조건이 만족할 때까지 대기하고 있는 장소다.
  • 스레드는 WaitSet에 들어가 대기할 때 Lock을 해제한다.
  • 다른 스레드에 의해 깨어나게 되면 EntrySet으로 이동해서 다시 Lock을 획득할 수 있다.

 

 

조건변수

  • 조건변수를 통해 상호 협력하고 있는 두 스레드가 wait()과 notify() 메서드 실행 후에 하나의 모니터를 두고 두 스레드 모두 소유가 가능한 상황이 발생한다.
  • 하나는 대기중인 스레드, 하나는 깨우는 스레드로서 어떤 스레드가 모니터를 먼저 소유할 것인가에 따라 두 종류의 조건 변수로 나눌 수 있는데 Stop and Wait와 Signal and Continue가 있다.

 

Signal and Wait

  • 현재 모니터를 소유하고 있는 스레드가 wait() 을 실행하면 모니터 내부에서 자신을 일시 중단하고 Lock 을 해제한 후 Wait Set 에 들어간다.
  • 깨우는 스레드가 notify() or notifyAll() 명령을 실행하면 Wait Set 에 있는 대기 스레드 중 하나 또는 모든 스레드를 깨우고 깨우는 스레드는 Lock 을 해제하고 대기한다.
  • 대기에서 깨어난 스레드가 Lock 을 획득한 후 모든 작업을 마치고 Lock 을 해제하면 깨운 스레드가 Lock 을 획득한 후 계속 작업을 진행한다.
  • 대기 스레드와 깨운 스레드 사이에 다른 스레드가 모니터를 소유할 수 없도록 원자적 실행이 보장되어야 한다.

 

Signal and Continue

  • 현재 모니터를 소유하고 있는 스레드가 wait() 을 실행하면 모니터 내부에서 자신을 일시 중단하고 Lock 을 해제한 후 Wait Set 에 들어간다.
  • 깨우는 스레드가 notify() or notifyAll() 명령을 실행하면 Wait Set 에 있는 대기 스레드 중 하나 또는 모든 스레드를 깨운다. 이때  일어난 스레드들은 Entry Set 으로 이동한다.
  • 깨우는 스레드는 Lock 을 계속 유지하면서 모든 작업을 완료하고 Lock 을 해제하면 Entry Set 에 대기하고 있는 모든 스레드가 Lock 을 획득하기 위해 경쟁한다.
  • 자바에서는 이 조건 변수 형식을 취하고있다.
  • 개인적으로 이게 더 자연스러운 것 같다.

 

synchronized

앞에서 모든 자바 객체는 모니터를 가지고 있고, 모니터를 사용하기 위해서는 synchronized 키워드를 사용해야한다고 언급했다.

이에 대해 더 살펴보자.

 

  • synchronized는 명시적 락을 구현하는 것이 아닌 자바에 내장된 락으로 이를 암묵적인 락(Intrinsic Lock), 모니터락(Monitor Lock)이라고 한다.
  • synchronized은 동일한 모니터를 객체에 대해 오직 하나의 스레드만 임계영역에 접근할 수 있도록 보장하며 모니터의 조건 변수를 통해 스레드간 협력으로 동기화를 보장해준다.
  • synchronized가 적용된 한 개의 메서드만 호출해도 같은 모니터의 모든 synchronized 메서드까지 락에 잠기게 되어 락이 해제될 때까지는 접근이 안되는 특징을 가지고 있다.
  • 락은 스레드가 synchronized에 들어가기전에 자동 확보되며 정상적이든 비정상적이든 예외가 발생해서든 해당 블록을 벗어날 때까지 자동으로 해제된다.

 

동기화 방법

인스턴스 메서드 동기화

  • 인스턴스 단위로 모니터가 동작하며 동일한 인스턴스 안에서 synchronized 가 적용된 곳은 하나의 락을 공유
  • 인스턴스가 여러개일 경우 인스턴스별로 모니터 객체를 가지므로 스레드는 모니터 별로 락을 획득해서 동기화 영역에 진입하고 빠져 나올 때 락을 해제 할 수 있다.

 

public class MyClass {
    public synchronized void syncMethod1() {
          //동기화가 필요한 영역
   } 
    public synchronized void syncMethod2() {
         //동기화가 필요한 영역
    }
    
    public static void main(String[] args) {
    	//인스턴스 메서드에 관한 동기화에 대해서 서로 영향이 없다.
        MyClass m1 = new MyClass().syncMethod1(); 
        MyClass m2 = new MyClass().syncMethod1(); 
    }
}

 

정적 메서드 동기화

  • 클래스 단위로 모니터가 동작하며 synchronized 가 적용된 곳은 하나의 락을 공유
  • 인스턴스와는 별개의 모니터를 가지고 임계 영역을 동기화 하기 때문에 인스턴스 단위로 메서드를 호출할지라도 악은 클래스 단위로 스레드간 공유
  • 클래스는 메모리에 오직 하나만 존재하므로 하나의 모니터를 공유해서 동기화 하고자 할 때 사용 할 수 있다.

 

public class MyClass {
    public static synchronized void syncMethod1() {
        //동기화가 필요한 영역
    }
    public static synchronized void syncMethod2() {
        //동기화가 필요한 영역
    }

    public static void main(String[] args) {
    	//클래스 인스턴스는 메모리에 1개만 존재.
        MyClass.syncMethod1();
        MyClass.syncMethod1();
    }
}

 

인스턴스 블록 동기화

  • 인스턴스 단위로 모니터가 동작하며 synchronized 가 적용된 곳은 하나의 락을 공유
  • 모든 인스턴스가 모니터를 가지기 때문에 모니터를 여러 인스턴스로 구분해서 동기화를 구성할 수 있다.
  • 클래스의 인스턴스가 여러 개일 경우 인스턴스별로 모니터 객체를 가지며 스레드는 모니터 별로 락을 획득해서 synchronized 영역을 진입하고 빠져 나올 때 락을 해제 할 수 있다.

 

public class MyClass {

    public void syncMethod1() {
        synchronized(this){
            //동기화가 필요한 영역
        }
    }

    public void syncMethod2() {
        synchronized(this){
            //동기화가 필요한 영역
        }
    }

    public static void main(String[] args) {
    	//다른 인스턴스이므로 다른 모니터
        new MyClass().syncMethod1();
        new MyClass().syncMethod1();
    }
}

 

정적 블록 동기화

  • 클래스 단위로 모니터가 동작하며 synchronized 가 적용된 곳은 하나의 락을 공유
  • 모든 클래스가 모니터를 가지기 때문에 모니터를 여러 클래스로 구분해서 동기화를 구성할 수 있다.
  • 클래스 모니터가 여러개일 경우 스레드는 모니터 별로 락을 획득해서 synchronized 영역을 진입하고 빠져 나올 때 락을 해제 할 수 있다.

 

public class MyClass {

    public static void syncMethod1() {
        synchronized(MyClass.class){
            //동기화가 필요한 영역
        }
    }

    public static void syncMethod2() {
        synchronized(YourClass.class){
            //동기화가 필요한 영역
        }
    }

    public static void main(String[] args) {
    	//클래스 인스턴스는 메모리에 1개. 동일한 모니터
        MyClass.syncMethod1();
        MyClass.syncMethod1();
    }
}

 

그 밖의 synchronized에서 알아야할 내용

재진입성

  • 모니터 내에서 이미 synchronized 영역에 들어간 스레드가 다시 같은 모니터 영역으로 들어갈 수 있는데, 이를 "모니터 재진입"이라고 한다.
  • 재진입 가능하다는 것은 락의 획득이 호출 단위가 아닌 스레드 단위로 일어난다는 것을 의미하며 이미 락을 획득한 스레드는 같은 락을 얻기 위해 대기할 필요 없이 synchronized 블록을 만났을 때 같은 락을 확보하고 진입한다.

 

상속

  • 상속하게 되면 자식은 부모의 락과 동일한 락을 가지게 된다
  • 동기화 된 메서드에서 다른 동기화 된 메서드를 호출하는 경우 이미 락(lock)을 가지고 있는 스레드가 같은 락을 확보하고 재진입 시 데드락이 발생하지 않고 정상적으로 진행할 수 있게 된다.

 

public class MyClass {

    static class Parent {
        public synchronized void method() {
            System.out.println("Parent method");
        }
    }

    static class Child extends Parent {
        @Override
        public synchronized void method() {
            System.out.println("start super");
            super.method();
            System.out.println("finish super");
        }
    }

    public static void main(String[] args) {
        Child child = new Child();
        child.method();
    }
}

 

가시성

  • synchronized 는 가시성을 지원
  • 가시성이란 한 스레드가 공유자원을 수정하거나 쓰기 작업을 했을 때 다른 스레드가 수정한 내용이 보이는 것이다.
  • cpu 캐시에 수정한 변수 값을 적지 않고 메모리에 직접 적는 것을 의미한다.

 

기타

  • sleep() 을 실행한 스레드는 동기화 영역에서 대기 중이더라도 획득한 락을 놓거나 해제하지 않는다.
  • synchronized 의 동기화 영역에 진입하지 못하고 대기 중인 스레드는 인터럽트 되지 않는다.
  • synchronized 의 동기화 영역에 진입하지 못하고 대기 중인 스레드가 다시 경쟁해서 모니터를 획득하는 것은 순서가 정해져 있지 않다.
  • wait(), notify(), notifyAll()은 모두 synchronized 블록 안에서만 사용해야 한다. 즉 모니터 락을 확보한 상태에서만 작동한다.

 

현대 애플리케이션은 여러 자바 애플리케이션 서버가 구동되므로 단일 jvm에서 작동하는 synchronized를 잘 사용하지 않는다. 

 

 

참고자료 

https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EB%A6%AC%EC%95%A1%ED%8B%B0%EB%B8%8C-part1/dashboard

 

반응형
'개발/Java' 카테고리의 다른 글
  • Java Virtual Thread 정리
  • Java Thread (version 2)
  • equals()와 hashCode()
  • Java MetaSpace
Debin
Debin
공부 기록을 남기며 게시글 리팩토링을 진행하는 블로그입니다.
  • Debin
    리팩토링하는 블로그
    Debin
  • 전체
    오늘
    어제
    • 분류 전체보기
      • DB
        • DB 기초
        • MySQL
        • SQL 튜닝
      • OS
      • Network
      • Git
      • 디지털콘텐츠기획
      • 소프트웨어공학
      • 코딩테스트
        • 프로그래머스
        • 백준
        • 인프런
      • 공부 일지
      • 독서
        • 클린코드
        • 일상 속 사물이 알려주는 웹 API 디자인
        • 토비의 스프링
        • 객체지향의 사실과 오해
        • 자바 잘 읽는 법
      • 기록 및 회고
      • Cloud
        • AWS
      • 개발
        • Java
        • Spring Core
        • Spring MVC
        • Spring DB
        • Spring Boot
        • Spring Security
        • Spring Batch
        • JPA
        • Test
        • Android
      • 대외활동
        • UMC SERVER
        • 카엔프 SW 아카데미
      • 프로젝트
      • Docker
      • Gradle
      • ELK
      • 실무 이야기
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

    • 본인 깃허브입니다!
  • 인기 글

  • 태그

    docker
    스프링 부트
    container
    컨테이너
    AOP
    코딩 #개발자 #노마드북클럽 #노개북
    프록시
    ORM
    JPA
    도커
    AWS
    인덱스
    spring mvc
    객체
    객체지향
    토비의 스프링
    innodb
    자바
    스프링
    spring
    리눅스
    test
    SQL
    Java
    트랜잭션
    redis
    운영체제
    spring boot
    mysql
    데이터베이스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
Debin
자바에서 모니터가 동작하는 원리 (with synchronized)
상단으로

티스토리툴바