이시안 개발 블로그

멀티 스레드의 임계 영역 본문

☕Java

멀티 스레드의 임계 영역

ICAN 2022. 1. 19. 10:40

💻 문제

 

멀티 스레드는 동시성과 병렬성으로 작업을 합니다.

 

동시성(Concurrency)

하나의 코어(싱글 코어)에서 여러 개의 스레드가 번갈아가며 실행하는 성질

 

병렬성(Parallelism)

멀티 코어에서 여러 개의 스레드를 동시에 실행하는 성질

 

여기서 멀티 스레드가 하나의 객체를 공유해서 작업하는 경우가 있는 데 그 객체를 공유 객체라고 합니다.

 

공유 객체를 사용하는 경우 의도하지 않은 결과가 나올 수도 있습니다. 스레드의 수가 적다면 이 문제를 해결하기 위해 wait(), notify(), join() 등의 메서드를 사용할 수 있겠지만 synchronized 키워드를 사용해 Thread-safe하게 해결할 수도 있습니다.

 

📚 동기화(Synchronization)

 

동기화란 여러 스레드가 공유되는 리소스를 사용할 때 하나의 스레드가 작업을 끝마칠 때까지 다른 스레드의 접근을 막는 것입니다.

 

synchrnoized는 하나의 스레드만 일할 수 있도록 임계 영역을 지정하는 키워드입니다.

 

임계 영역

단 하나의 스레드만 실행할 수 있는 코드 영역으로 동기화 메서드, 동기화 블록으로 사용할 수 있습니다.

인스턴스마다 1개의 lock이 있으며 임계 영역의 코드를 먼저 실행하는 스레드가 lock을 가지게 됩니다.

// 동기화 메서드
public synchronized void method() {}

// 동기화 블록
public void method() {
        // 여러 스레드가 실행 가능한 영역
        ...
        
        synchronized(공유객체) {
            임계 영역 // 단 하나의 스레드만 실행 가능
        }
        
        // 여러 스레드가 실행 가능한 영역
}

동기화 메서드는 메서드 전체가 임계 영역으로 설정됩니다. 이 메서드를 스레드 A가 호출해서 실행하는 동안 스레드 B는 해당 객체의 다른 임계 영역에 접근할 수 없게 됩니다.

 

동기화 메서드가 끝날 때까지 다른 스레드가 일시정지하게 되므로 스레드의 병목현상이 발생하게 되며 이는 성능과 밀접한 연관이 있습니다. 때문에 이를 방지하기 위해 임계 영역을 최소화하는 것이 좋습니다.

 

동기화 블록은 위 문제를 해결하기 위한 방법으로 최소한의 임계 영역을 지정할 수 있습니다. 참조 변수로는 공유 객체가 들어가며 this 키워드를 사용하면 현재 객체에 lock을 걸게 됩니다.

 

여기서 객체에 lock이 걸린다는 의미는 모든 스레드가 객체에 접근을 못하는 것이 아니라, synchronized 키워드로 지정된 임계 영역에 들어서면 일시정지한다는 것입니다.

 

📖 실험

SyncTest 클래스
public class SyncTest {
	
    // 인스턴스 메서드(일반 메서드)
    public void methodA() {
        for (int i = 0; i < 100; i++) {
            System.out.print("일반");
        }
    }

    // 동기화 메서드(임계 영역)
    public synchronized void methodB() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.print("ㅡ");
        }
    }

    // 동기화 블록(임계 영역)
    public void methodC() throws InterruptedException {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                System.out.print("ㅣ");
            }
        }
    }
}

임계 영역이 어떻게 작동하는지 알아보기 위해 일반 메서드, 동기화 메서드, 동기화 블록을 가진 메서드를 가진 간단한 클래스를 작성했습니다. 반복문을 100번씩 루프 하면서 콘솔에 찍히는 것을 확인할 수 있도록 했습니다.

 

Main 클래스
public class Main {
    public static void main(String[] args) {
    
        SyncTest sync = new SyncTest();
        
        // 일반 메서드 호출
        Thread threadA = new Thread(sync::methodA);
        
        // 동기화 메서드 호출
        Thread threadB = new Thread(() -> {
                    try {
                        sync.methodB();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );
        
        // 동기화 블록을 가진 메서드 호출
        Thread threadC = new Thread(() -> {
                    try {
                        sync.methodC();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        );
        
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

 

 

세 개의 메서드를 동시에 실행하도록 세 개의 스레드를 생성해서 실행시켰습니다.

 

☄️ 결과

콘솔 결과

일반 메서드는 임계 영역과 관계가 없으므로 객체에 lock이 걸려있어도 그냥 본인의 작업을 했습니다.

"ㅡ"를 출력하는 동기화 메서드가 먼저 임계 영역에 접근했으므로 "ㅣ"를 출력하는 동기화 블록을 가진 메서드는 lock이 걸려 가장 나중에 작업을 수행한 모습입니다.

 

임계 영역에 대한 개념이 어려웠는데 여러 블로그랑 책을 보면서 어느정도 감이 잡힌 것 같습니다.

제가 잘못 생각하고 있거나 다른 의견이 있으시면 댓글 부탁드립니다.

 

감사합니다.

 

참고
이것이 자바다
자바의 정석
https://beststar-1.tistory.com/21?category=976609
Comments