티스토리 뷰

Java/Java

<Java> sychronized이란?

면목동인간 2024. 10. 22. 16:11

sychronized이란?

 java의 키워드이며 메서드에 선언한다. 선언된 메서드는 멀티스레드 상황에서 하나의 스레드만 실행할 수 있다. 아래 예제를 통해 알아보았다.

 

sychronized 사용X 예제

Main.java

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Runnable task = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10000; i++) {
                    counter.increment();
                }
            }
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("결과: " + counter.getCount());
    }
    static class Counter {
        private int count = 0;
        public void increment() {
            count = count + 1;
        }
        public int getCount() {
            return count;
        }
    }
}

실행 결과

결과: 11355

 예상한 실행 결과는 20000이어야 하는데 예상하지 못한 값이 출력되었다. 그 이유는 동시성으로 인한 문제로 발생되는데 아래 그림으로 나타냈다. 실행 결과는 환경에 따라 다를 수 있다.

 위의 그림을 보면 thread1,2 둘 다 거의 동시에 count 변수에 접근하게 된다. 그런데 여기서 문제가 발생하는데 실행하는 메서드는 count = count+1이다. 이 메서드는 3가지로 나눌 수 있는데 1. count 변수를 조회, 2. count에 1 더하기, 3. 결과를 count에 대입하는 과정이다. 그런데 만약 thread1이 1번을 실행하는 중에 thread2도 1을 수행한다면 같은 변수를 읽게 된다. 결국 같은 변수에 대해 똑같은 작업을 실행 후 대입하게 된다. 결국 동시성 문제가 발생하게 된다.

 

해결 방법

 위의 동시성 문제를 해결하기 위해 임계 영역인 count = count+1를 스레드 하나만 실행하도록 안전하게 보호해야 한다. 여기서 임계 영역이란 영어로 critical section이라고 하는데 여러 스레드가 동시에 접근하면 데이터 불일치나 예상치 못한 동작이 발생할 수 있는 위험하고 중요한 코드 부분이다. 앞선 예제에서 count 변수를 읽거나 수정하는 부분이 임계 영역이라고 볼 수 있다. 해결 방법은 sychronized를 통해 임계 영역을 보호할 수 있다.

 

sychronized 사용O 예제

Main.java

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Runnable task = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10000; i++) {
                    counter.increment();
                }
            }
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("결과: " + counter.getCount());
    }
    static class Counter {
        private int count = 0;
        public synchronized void increment() {
            count = count + 1;
        }
        public int getCount() {
            return count;
        }
    }
}

 위의 increment 메서드에 sychronized 키워드를 추가했다.

 

실행 결과

결과: 20000

 위의 실행 결과를 보면 20000이 출력되었다. sychronized 키워드를 추가하면 thread1이 메서드를 실행하는 동안 thread2는 BLOCKED(락을 획득할 때까지 대기 상태, CPU 스케줄링에 들어가지 않음) 처리가 된다. 그리고 thread2는 thread1의 메서드가 종료되면 메서드를 실행한다. 아래 그림은 스레드 상태를 그림으로 나타냈다.

 

 위의 그림에서 sychronized 키워드가 붙은 메서드를 실행하려면 인스턴스의 락이 필요하다. 그러면 다른 스레드는 락을 획득할 때까지 BLOCKED 처리가 되며 락이 반납될 때까지 대기 후 락을 획득하면 메서드를 실행한다. 위의 예제는 각 스레드에서 for문을 실행하는데 각 스레드가 메서드를 번갈아가면서 락을 획득 후 반납을 반복하며 실행하게 된다.

 

임계 영역 분리

 메서드를 통째로 sychronized를 걸게 되면 여러 스레드가 동시에 접근해도 문제가 없는 부분까지 실행할 수 없게 된다. 이럴 때 임계 영역을 분리하여 그 부분만 sychronized를 걸면 된다.

public void increment() {
    // 여러 스레드가 접근 가능한 코드
    synchronized (this) {
        count = count + 1;
    }
    // 여러 스레드가 접근 가능한 코드
}

 위의 코드를 보면 메서드에 sychronized를 선언하지 않고 메서드 내부에 안전한 임계 영역 블록을 넣었으며 블록 내에 임계 영역 코드를 넣으면 된다. sychronized를 사용하기 위해 인스턴스의 락이 필요하므로 인스턴스의 참조인 this를 넣어주면 된다.

 

정리

 sychronized 키워드는 여러 스레드가 동시에 접근할 수 있는 자원에 대해 일관성 있고 안전한 메커니즘이다. 하지만 스레드가 무수히 많은 상태에서 나머지 스레드는 스레드가 실행 중인 메서드가 종료될 때까지 계속 대기해야 하며 종료되더라도 나머지 스레드의 실행 순서는 보장되지 않는다. (실행 시점은 thread1->2->3이지만 정작 실행은 thread1->3->2가 될 수도 있다.)

 


본 포스팅은 “김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성/인프런”를 학습한 내용을 정리한 것

댓글
최근에 올라온 글
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함
Total
Today
Yesterday