백엔드/뎁스스터디

5- 동시성 처리 (1)

넌 감동란이었어 2024. 5. 20. 22:16

동시성 처리에 대해 알아보자

 

동시성을 알아보기 전에 알아야하는 배경지식들이 많아 그거에 대해 먼저 알아보자.

 

동시성을 검색해보면 스레드 개념이 나온다.

스레드란 무엇일까??

 

스레드(Thread)

 

특정 작업을 수행하는 소프트웨어를 프로그램이라고 한다.

이러한 프로그램이 실제로 실행되어 메모리나 CPU와 같은 자원을 할당받으면 이를 프로세스라고 한다.

스레드는 이 프로세스를 구성하는 하나의 단위이다.

하나의 프로세스에는 여러 스레드가 작동할 수 있다.

 

아무래도 같은 프로세스안에 여러 스레드가 존재하기 때문에 같은 자원을 공유할 수 있다. 같은 자원을 공유할 수 있다는 말은 동시에 여러가지 일을 같은 자원을 두고 수행할 수 있다는 것이고 이는 곧 병렬성의 향상으로 이어진다.

 

물론 여러 스레드에 대해 장점만 있는건 아니다. 여러 스레드가 동시에 하나의 자원을 공유하기 때문에 에이 자원을 두고 여러 문제점이 발생한다. ex) 동시성 문제, 데드락 등등

 

스레드 안정성이 깨지는 상황

 

ex) 1 조회수 계산 프로그램

public class CountingTest {
    public static void main(String[] args) {
        Count count = new Count();
        for (int i = 0; i < 100; i++) {
            new Thread(){
                public void run(){
                    for (int j = 0; j < 100; j++) {
                        System.out.println(count.view());
                    }
                }
            }.start();
        }
    }
}
class Count {
    private int count;
    public int view() {return count++;}
    public int getCount() {return count;}
}

이 프로그램을 실행하면 100명*100번 조회이므로 100000번의 조회수가 나와야한다. 하지만 그보다 더 적은 조회수가 나온다. 그 이유는 count ++는 사실상 조회후 증가인 프로세스를 거치는데 그 때 여러 스레드가 접근하여 값이 달라지기 때문이다.

1. count 변수값 조회

2. 조회한 count 변수 값에 1을 더한 값을 저장

 

아래와 같은 동시성 이슈가 발생한다.

우리는 조회수를 계산할 때 한번 접근에 꼭 1의 조회수가 더해지는 것을 바란다. 그렇다면 동시성 이슈를 해결하기 위해서는 어떻게 해야할까??

 

동시성을 제어하는 방법

가장 쉬운방법은 Lock을 걸어버리는 것이다. Lock은 하나의 스레드가 해당 메서드에 실행하고 있으면 다른 메서드가 햐당 메서드를 실행하지 못하고 대기하게 된다. 즉 한번에 하나의 스레드만 접근할 수 있게 된다. 하지만 이는 병렬성이 매우 낮아지게 된다. 문제가 된 view 메서드에 synchronized 키워드를 붙이면 암시적 락이 걸리게 된다.

 

lock은 메서드, 변수에 각각 걸 수 있다. 메서드에 lock을 걸 경우 해당 메서드에 진입하는 스레드는 단 하나만 가능하다. 변수에 lock을 걸 경우 해당 변수를 단 하나의 스레드만 참조할 수 있다. 단, 변수에 lock을 걸기 위해선 해당 변수는 객체여야만 합니다. int, long과 같은 기본형 타입에는 lock을 걸 수 없다.

메서드 Lock

class Count {
    private int count;
    public synchronized int view() {return count++;}
}

 

변수 Lock

class Count {
    private Integer count = 0;
    public int view() {
        synchronized (this.count) {
            return count++;
        }
    }
}

명시적 Lock

synchronized 키워드 없이 명시적으로 ReentrantLock을 사용하는 Lock을 명시적 Lock이라고 부른다. 해당 Lock의 범위를 메서드 내부에서 한정하기 어렵거나, 동시에 여러 Lock을 사용하고 싶을 때 사용한다. 직접적으로 Lock 객체를 생성하여 사용할 수 있다. lock() 메서드를 사용할 경우 다른 스레드가 해당 lock() 메서드 시작점에 접근하지 못하고 대기하게 된다. unlock() 메서드를 실행해야 다른 메서드가 lock을 획득할 수 있게 된다.

lock을 좀 더 다양하게 관리하거나 복잡한 상황을 컨트롤해야 할 수도 있고, 특정 시간을 기다리고 안 되면 무시하고 지나가야 한다던지 lock을 잡는 순서를 보장해야 하는 경우가 있을 수 있다. 그 때 사용해야 하는 것이 바로 ReentrantLock이다.

 

명시적 Lock을 사용한 예제

public class CountingTest {
    public static void main(String[] args) {
        Count count = new Count();
        for (int i = 0; i < 100; i++) {
            new Thread(){
                public void run(){
                    for (int j = 0; j < 1000; j++) {
                        count.getLock().lock();
                        System.out.println(count.view());
                        count.getLock().unlock();
                    }
                }
            }.start();
        }
    }
}
class Count {
    private int count = 0;
    private Lock lock = new ReentrantLock();
    public int view() {
            return count++;
    }
    public Lock getLock(){
        return lock;
    };
}

자원의 가시성을 책임지는 volatile

... 이내용은 추후로 공부를 한 후 작성하겠다.

아직 무슨 소리인지 모르겠다.

 

ConcurrentHashMap

현업에서 제일 많이 사용하고 김영한 강사님도 언급한 ConcurrentHashMap이다.

 

이건 다음 내용에서 다룬다.

다음에는 deadLock, Context Switch 같은 문제도 다룰 것 이다.

 

출처

https://deveric.tistory.com/104

 

[Java] Multi Thread환경에서 동시성 제어를 하는 방법

스레드(Thread)란 무엇일까요? 스레드가 무엇인지 설명하기 위해서는 그 상위 단위인 프로세스에 대해 이해할 필요가 있습니다. 일반적으로 특정 작업을 수행하는 소프트웨어를 우린 프로그램이

deveric.tistory.com

 

'백엔드 > 뎁스스터디' 카테고리의 다른 글

6- ci/cd (1)  (0) 2024.06.18
5- 동시성 처리 (2)  (1) 2024.05.28
4- 스프링 시큐리티 + jwt(2)  (0) 2024.05.13
3- 스프링 시큐리티 + jwt (1)  (0) 2024.05.07
2- TDD(2)  (1) 2024.04.07