자바의 synchronized 키워드 동기화 범위 정리하기!

2024. 3. 23. 17:23· 언어/JAVA
목차
  1. 1. synchronized method
  2. 2. synchronized static method
  3. 3. synchronized method와 synchronized static method 함께 사용하기
  4. 왜?
  5. 정리하기
  6. 레퍼런스
728x90

이전에 게시했던 ReentrantLock과 유사하게 자바에선 synchronized 키워드를 통해 특정 클래스 및 인스턴스에 대한 동기화를 제공한다.

 

개인적으론 synchronized 키워드를 직접 코드단에 사용하기보단 코어한 라이브러리들을 뜯어보며 발견한 경우가 대부분이었기에 동기화라는 키워드로 느낌만 대충 이해하고 있었다.

 

하지만 synchronized는 사용 방식에 따라 락 범위에 약간의 차이가 있고, 이를 이해하지 않고 사용하면 예상치 못한 문제를 만나게 될 수 있다.

 

synchronized를 락의 관점에서 정리해보자!

 

1. synchronized method

아래와 같이 메서드 단위로 synchronized를 선언할 수 있다.

public class SampleClass {
    
    public synchronized void synchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

메서드 단위의 synchronized는 인스턴스에 락을 건다.

 

따라서, 동일 인스턴스에서 해당 메서드를 동시에 호출하면 동기화가 보장된다.

public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        SampleClass instance = new SampleClass();

        Runnable runnable1 = () -> instance.synchronizedPrint("1");
        Runnable runnable2 = () -> instance.synchronizedPrint("2");

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

물론 인스턴스에 대하여 락을 건다는 것은 synchronized가 선언된 메서드부에 한해서만 적용된다.

 

다시 말해, 아래와 같이 동일 인스턴스에서 synchronized가 선언된 메서드와 그렇지 않은 메서드를 동시에 실행하더라도 synchronized가 선언되지 않은 메서드까지 락이 걸리진 않는다.

public class SampleClass {

    public synchronized void synchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void nonSynchronizedPrint(String callNumber) {
        System.out.println(callNumber + " is non synchronized printing.");
    }
}
public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        SampleClass instance = new SampleClass();

        Runnable runnable1 = () -> instance.synchronizedPrint("1");
        Runnable runnable2 = () -> instance.nonSynchronizedPrint("2");

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

 

그렇다면 만약 서로 다른 두 메서드에 대해 synchronized를 선언하고 이를 동시에 실행시키면 동기화가 일어날까?

public class SampleClass {

    public synchronized void synchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void anotherSynchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing in another synchronized print.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print in another synchronized print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        SampleClass instance = new SampleClass();

        Runnable runnable1 = () -> instance.synchronizedPrint("1");
        Runnable runnable2 = () -> instance.anotherSynchronizedPrint("2");

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

동기화가 발생했다!

 

정리하면, synchronized가 선언된 메서드는 동일 인스턴스의 synchronized가 선언된 다른 메서드들과도 락을 공유한다고 할 수 있다.

2. synchronized static method

그렇다면 static 메서드에 synchronized를 선언하면 어떻게 될까?

 

static 으로 선언된 메서드 및 변수들은 인스턴스에서 관리되는 값이 아니므로 아예 클래스 단위로 락이 걸릴 것이라고 예상된다.

 

아래의 코드를 통해 테스트해보자.

public class SampleClass {

    public static synchronized void staticSynchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing in static synchronized print.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print in static synchronized print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Runnable runnable1 = () -> SampleClass.staticSynchronizedPrint("1");
        Runnable runnable2 = () -> SampleClass.staticSynchronizedPrint("2");

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

예상했듯 static으로 선언된 메서드는 클래스 단위로 동기화된다!

 

그렇다면 서로 다른 두 인스턴스가 synchronized가 선언되지 않은 메서드에서 static synchronized 메서드를 호출하면 어떻게 될까?

public class SampleClass {
    public static synchronized void staticSynchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " is printing in static synchronized print.");
            Thread.sleep(2);
            System.out.println(callNumber + " finished print in static synchronized print.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void runStaticSynchronizedPrint(String callNumber) {
        try {
            System.out.println(callNumber + " trying to start static synchronized print");
            Thread.sleep(2);
            staticSynchronizedPrint(callNumber);
            System.out.println(callNumber + " trying to finish static synchronized print");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        SampleClass instance1 = new SampleClass();
        SampleClass instance2 = new SampleClass();

        Runnable runnable1 = () -> instance1.runStaticSynchronizedPrint("1");
        Runnable runnable2 = () -> instance2.runStaticSynchronizedPrint("2");

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

테스트 결과 각 인스턴스는 synchronized가 선언되지 않은 메서드를 동기화없이 호출하지만, static synchronized가 선언된 메서드 접근 시 동기화되는 것을 확인할 수 있다!

 

3. synchronized method와 synchronized static method 함께 사용하기

만약 synchronized method와 synchronized static method를 함께 사용한다면 동기화가 어떻게 일어날까?

public class SampleClass {
    public synchronized void synchronizedPrint() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("sync print method called. i = " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void staticSynchronizedPrint() {
        System.out.println("static sync print method called");
    }
}

위와 같이 synchronized method와 static synchronized method를 모두 정의한 후, 서로 다른 스레드에서 호출해보자.

public class Main {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        SampleClass instance = new SampleClass();

        Runnable runnable1 = () -> instance.synchronizedPrint();
        Runnable runnable2 = () -> SampleClass.staticSynchronizedPrint();

        executorService.submit(runnable1);
        executorService.submit(runnable2);
    }
}

오.. synchronized method와 static synchronized method는 서로를 동기화하지 않는 것으로 확인됐다!

왜?

지금껏 코드를 일일이 실행시켜보며 각 케이스를 점검해보았고, 이젠 왜 위와 같은 동기화 방식이 일어나는지 알아보자.

 

synchronized는 자바 객체의 Monitor를 통해 Metex 방식의 락을 사용한다.

 

각 객체는 인스턴스 Monitor와 클래스 Monitor를 관리해 사용하며, 이 두개는 각각 synchronized 메서드와 static synchronized 메서드에서 사용된다.

 

정리하기

위 내용을 최종적으로 정리하면 다음과 같다!

 

1. static으로 선언되지 않은 synchronized는 synchronized가 선언된 인스턴스의 모든 메서드와 락을 공유한다. 즉, 서로 다른 synchronized 메서드이더라도 동기화된다.

 

2. synchronized로 선언된 메서드가 인스턴스 모든 메서드 자체에 락을 거는 것은 아니다. 따라서 synchronized가 선언되지 않은 메서드들까지 동기화가 일어나진 않는다.

 

3. static으로 선언된 synchronized 메서드는 클래스 단위로 락을 걸고, 이는 인스턴스 synchronized 메서드와 서로 다른 락이므로 서로간 동기화되지 않는다.

 

레퍼런스

https://jgrammer.tistory.com/entry/Java-%ED%98%BC%EB%8F%99%EB%90%98%EB%8A%94-synchronized-%EB%8F%99%EA%B8%B0%ED%99%94-%EC%A0%95%EB%A6%AC

 

[Java] 혼동되는 synchronized 동기화 정리

synchronized는 lock을 사용해 동기화를 시킨다. 하지만 사용 방식에 따라 혼동되기 쉽다. synchronized는 4가지의 사용법이 있다. sychronized method, sychronized block, static sychronized method, static synchonized block. 이

jgrammer.tistory.com

 

저작자표시 (새창열림)

'언어 > JAVA' 카테고리의 다른 글

JVM의 기본적인 내용 정리해보기!  (1) 2024.04.03
자바의 ReentrantLock 가볍게 알아보기!  (0) 2024.02.28
동시성을 검증하는 테스트 코드 작성하기!  (1) 2024.02.25
[JAVA] Optional의 orElse()와 orElseGet()의 차이점  (1) 2022.08.26
  1. 1. synchronized method
  2. 2. synchronized static method
  3. 3. synchronized method와 synchronized static method 함께 사용하기
  4. 왜?
  5. 정리하기
  6. 레퍼런스
'언어/JAVA' 카테고리의 다른 글
  • JVM의 기본적인 내용 정리해보기!
  • 자바의 ReentrantLock 가볍게 알아보기!
  • 동시성을 검증하는 테스트 코드 작성하기!
  • [JAVA] Optional의 orElse()와 orElseGet()의 차이점
SeongOnion
SeongOnion
서버는 꺼지지 않아요
SeongOnion
조무래기 코딩
SeongOnion
전체
오늘
어제
  • 분류 전체보기 (167)
    • 알고리즘 (81)
      • 이론 (8)
      • 문제풀이 (73)
    • 언어 (15)
      • Python (9)
      • JavaScript (1)
      • JAVA (5)
    • 데이터베이스 (5)
    • 프레임워크 (15)
      • Django (7)
      • Spring (8)
    • 그 외 공부 (38)
      • 운영체제 (1)
      • 자료구조 (14)
      • 네트워크 (5)
      • CS (2)
      • 기타 (7)
      • 트러블 슈팅 (9)
    • 프로젝트 (0)
    • 개발자취 (8)
    • 회고 (3)
    • 주저리주저리 (1)
    • 기타 (비개발) (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 정렬 알고리즘
  • 그리디알고리즘
  • 개발자
  • Django
  • 회고
  • spring
  • DP
  • 투 포인터 알고리즘
  • 백준
  • 자바
  • 장고
  • 브루트포스
  • 컨트리뷰트
  • 트러블 슈팅
  • 스택
  • 소수
  • 알고리즘
  • 데이터베이스
  • 웹
  • 코딩테스트
  • 프로그래머스
  • 파이썬
  • 에라토스테네스의 체
  • 코딩
  • 큐
  • DRF
  • BFS/DFS
  • 오픈소스
  • BFS
  • 이진탐색

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
SeongOnion
자바의 synchronized 키워드 동기화 범위 정리하기!
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.