Race Condition in Java

Jeonghwa·2023년 1월 23일
0

Race Condition in Java

Java는 multi-thread 프로그래밍 언어이며 race condition이 발생할 위험이 높다. 동일한 리소스를 여러 스레드에서 동시에 접근할 수 도 있고 데이터가 변경될 수 도 있기 때문이다.
race condition은 동시성 버그라고 말할 수 있으며 deadlock과 밀접한 관련이 있다.


Race Condition이란

임계영역(critical section : 공유 메모리에 접근하는 프로그램의 일부)이 두 개 이상의 스레드에 의해 동시에 실행되는 조건이다. 다시 말해, 특정 공유 리소스를 얻기 위해 두개 이상의 스레드가 함께 경쟁하는 조건으로 정의할 수 있으며 이는 프로그램의 잘못된 동작으로 이어진다.

예를 들어 스레드A가 연결목록에서 데이터를 읽고 있고, 스레드B가 동일한 데이터를 삭제하려고 하는 경우, 이 프로세스로 인해 런타임 오류가 발생할 수 있다.

이러한 Race Conditions에는 2가지 유형이 있다

  1. Read - modifiy - write (읽기 수정 쓰기)
  • 동시에 2개이상의 스레드가 값을 읽은 후 수정하고 덮어쓰게 된다면 예상치 못한 결과가 나온다
counter++;
  1. Check - then - act (확인 후 조치)
  • 스레드 A map의 key확인 완료
  • 스레드 B map의 key확인 완료
  • 스레드 A map의 value제거
  • 스레드 B map의 value제거 수행 시 오류 발생 (이미 제거 되었기 때문)
	if(map.contaions(key)){
    	map.remove(key)
    }

문제

예시
3개의 쓰레드가 동기화 없이 공유하는 변수 c를 +1증가시킨 후 -1감소시킨다.

public class Counter implements Runnable {
    private int c = 0;

    public void increment() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            //Auto-generated catch block
            e.printStackTrace();
        }
        c++;
    }

    public void decrement() {
        c--;
    }

    public int getValue() {
        return c;
    }

    @Override
    public void run() {
        //incrementing
        this.increment();
        System.out.println("Value for Thread After increment " + Thread.currentThread().getName() + " " + this.getValue());
        //decrementing
        this.decrement();
        System.out.println("Value for Thread at last " + Thread.currentThread().getName() + " " + this.getValue());
    }
}
public class RaceConditionDemo {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(counter, "Thread-1");
        Thread t2 = new Thread(counter, "Thread-2");
        Thread t3 = new Thread(counter, "Thread-3");
        t1.start();
        t2.start();
        t3.start();
    }
}

결과
1과 0을 반복하며 나올 것이라는 예상과 다르게 c가 잘못된 값을 제공하는 것을 관찰할 수 있다.

Value for Thread After increment Thread-1 2
Value for Thread After increment Thread-3 2
Value for Thread After increment Thread-2 2
Value for Thread at last Thread-1 1
Value for Thread at last Thread-3 0
Value for Thread at last Thread-2 -1

해결방법

  1. 상호배제(Mutual exclusion)
  • 스레드가 공유 변수 또는 공유 스레드를 사용하는 경우 다른 스레드가 동일한 작업을 수행하지 못하도록 배제한다.
  1. 프로세스 동기화(Synchronize the process)
  • 한번에 하나의 프로세스만 공유데이터에 액세스할 수 있도록 한다.

Java에서는 동기화 기법으로 상호배제(보완된 세마포어)를 구현한 Monitor를 Object내부에 구현하여 모든 인스턴스에 Thread동기화를 가능하게 한다.
이 Monitor를 활용하여 상호배제하기 위해서는 synchronized키워드를 사용해야하는데 사용하는 방법으로는 메서드 앞에 키워드를 붙이거나, 메서드 내부에 synchronized(모니터 인스턴스){구현}으로 사용할 수 있다.

synchronized : synchronized 메서드를 사용하려면 모든 Thread는 lock을 가지고 있어야하며 이는 2가지 효과를 가진다

  • 만약 스레드A가 한 객체의 동기화된 메서드를 호출 중이라면, 스레드B는 동일한 객체의 동기화된 메서드를 호출할 수 없다. 따라서 스레드B는 스레드A의 작업이 완료될 때 까지 차단(실행 일시 중단)된다.
  • 스레드A의 동기화된 메서드가 종료되면, 사전 발생 관계(스레드B의 후속 호출)를 자동으로 설정한다. 이렇게 하면 객체의 상태 변화를 모든 스레드에서 볼 수 있다.
    출처 :docs.oracle - Synchronized Methods

예시
증가와 감소 연산 시 다른 스레드가 끼어들지 못하게 run 메서드 내부에 동기화 block을 만들었다.

public class Counter implements Runnable {
    private int c = 0;

    public void increment() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            //Auto-generated catch block
            e.printStackTrace();
        }
        c++;
    }

    public void decrement() {
        c--;
    }

    public int getValue() {
        return c;
    }

    @Override
    public void run() {
        synchronized (this){
            //incrementing
            this.increment();
            System.out.println("Value for Thread After increment " + Thread.currentThread().getName() + " " + this.getValue());
            //decrementing
            this.decrement();
            System.out.println("Value for Thread at last " + Thread.currentThread().getName() + " " + this.getValue());
        }
    }
}

결과

Value for Thread After increment Thread-1 1
Value for Thread at last Thread-1 0
Value for Thread After increment Thread-3 1
Value for Thread at last Thread-3 0
Value for Thread After increment Thread-2 1
Value for Thread at last Thread-2 0

참고 :
javatpoint - Race Condition in Java
stackoverflow - Race conditions "check-then-act" and "read-modify-write"
tecoble - java synchronize

profile
backend-developer🔥

0개의 댓글