Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

Java 스레드 간섭(Thread Interference) 오류란? 원인과 해결 방법


여러 스레드가 동시에 하나의 공유 변수에 접근해 읽기·쓰기를 수행하면, 각 스레드의 연산이 서로 엇갈리면서 최종 결과가 예상과 달라지는 경우가 있습니다. 이러한 현상을 스레드 간섭(Thread Interference) 오류라고 하며, 자바에서 대표적인 동시성(concurrency) 문제 중 하나입니다. 아래 예제를 통해 구체적으로 살펴보겠습니다.

예제 코드

import java.io.*;
class Demo_instance{
    static int val_1 = 6;
    void increment_val(){
        for(int j=1;j<11;j++){
            val_1 = val_1 + 1;
            System.out.println("증가 후 i의 값 : "+val_1);
        }
    }
    void decrement_val(){
        for(int j=1;j<11;j++){
            val_1 = val_1 - 1;
            System.out.println("감소 후 i의 값 : "+val_1);
        }
    }
}
public class Demo{
    public static void main(String[] args){
        System.out.println("Demo_instance 인스턴스 생성");
        System.out.println("스레드 인스턴스 생성");
        final Demo_instance my_inst = new Demo_instance();
        Thread my_thread_1 = new Thread(){
            @Override
            public void run(){
                my_inst.increment_val();
            }
        };
        Thread my_thread_2 = new Thread(){
            @Override
            public void run(){
                my_inst.decrement_val();
            }
        };
        my_thread_1.start();
        my_thread_2.start();
    }
}

실행 결과

Demo_instance 인스턴스 생성
스레드 인스턴스 생성
증가 후 i의 값 : 7
증가 후 i의 값 : 7
감소 후 i의 값 : 6
증가 후 i의 값 : 8
감소 후 i의 값 : 7
증가 후 i의 값 : 8
증가 후 i의 값 : 8
감소 후 i의 값 : 7
증가 후 i의 값 : 9
감소 후 i의 값 : 8
감소 후 i의 값 : 7
감소 후 i의 값 : 6
감소 후 i의 값 : 5
감소 후 i의 값 : 4
감소 후 i의 값 : 3
감소 후 i의 값 : 2
증가 후 i의 값 : 3
증가 후 i의 값 : 4
증가 후 i의 값 : 5
증가 후 i의 값 : 6

코드 설명

Demo_instance 클래스는 정적(static) 변수 val_1을 선언하고, 반복문을 돌며 값을 1씩 증가시킨 뒤 콘솔에 출력하는 increment_val() 메서드와, 같은 방식으로 값을 1씩 감소시키며 출력하는 decrement_val() 메서드를 가지고 있습니다.

Demo 클래스의 main 함수는 먼저 Demo_instance 객체를 생성한 뒤, 익명 내부 클래스 형태로 두 개의 스레드를 만듭니다. 첫 번째 스레드는 run() 메서드를 오버라이드하여 increment_val()을 호출하고, 두 번째 스레드는 decrement_val()을 호출합니다. 마지막으로 두 스레드를 start() 함수로 실행하면, 두 스레드가 동시에 같은 변수 val_1을 수정하게 됩니다.

스레드 간섭이 발생하는 이유

실행 결과를 보면 증가와 감소가 순서대로 진행되지 않고 뒤엉켜 있는 것을 확인할 수 있습니다. val_1 = val_1 + 1은 단일 연산처럼 보이지만, 실제로는 ① 현재 값 읽기 → ② 값 더하기 → ③ 결과 저장의 세 단계로 나뉩니다. 두 스레드가 이 단계들을 교차해서 수행하면 한 스레드가 저장한 결과를 다른 스레드가 덮어쓰게 되고, 그 결과 10번의 증가와 10번의 감소가 실행되었음에도 중간 값들이 기대와 다르게 출력됩니다. 이것이 바로 스레드 간섭 오류입니다.

스레드 간섭 방지 방법

가장 간단한 해결책은 공유 자원에 접근하는 메서드를 synchronized로 선언하여, 한 시점에 하나의 스레드만 실행하도록 만드는 것입니다.

synchronized void increment_val(){
    for(int j=1;j<11;j++){
        val_1 = val_1 + 1;
        System.out.println("증가 후 i의 값 : "+val_1);
    }
}

이 외에도 java.util.concurrent.atomic 패키지의 AtomicInteger를 사용하면 락(lock) 없이도 원자적(atomic) 연산을 보장할 수 있어, 상황에 따라 성능 면에서 더 유리한 선택이 될 수 있습니다.