본문 바로가기

프로그래밍 언어(Programming Language)/Java

쓰레드의 메모리 구성

 이번에는 쓰레드의 메모리 구성에 대해서 설명해보겠다. 쓰레드가 생성되면 가상머신은 쓰레드의 실행을 위한 별도의 메모리 공간을 할당한다고 설명하였다. 그렇다면 이러한 별도의 메모리 공간은 정확히 무엇을 의미하는 것일까?

 쓰레드의 가장 큰 역할은 별도의 실행흐름 형성이다. 그리고 별도의 실행흐름은 메소드의 호출을 통해서 형성된다. 즉 처음에는 run 메소드가 호출되고, run 메소드 내에서는 또 다른 메소드를 호출하면서 main 메소드와는 다른 흐름을 형성한다. 이렇듯 main 메소드와는 전혀 다른 실행흐름을 형성하기 위해서는 별도의 스택이 쓰레드에게 할당되어야 한다. 따라서 main 쓰레드 이외의 두 개의 쓰레드가 추가로 생성되면, 가상머신은 다음의 형태로 메모리를 구성한다. 

쓰레드에 할당되는 메모리

 위 그림에서 보듯이 모든 쓰레드는 자신의 스택을 할당 받는다. 그러나 힙과 메소드 영역은 모든 쓰레드가 공유한다. 여기서 특히 힙이 공유됨에 주목하자. 힙 영역이 공유된다는 것은 모든 쓰레드가 동일한 힙 영역에 접근이 가능함을 의미하는 것이고, 이는 다음과 같은 일이 가능함을 의미하는 것이다.

"A 쓰레드가 만든 인스턴스의 참조 값(사실상 주소 값)만 알면 B 쓰레드도 A 쓰레드가 만든 인스턴스에 접근 가능하다."

 그래서 쓰레드 사이에 데이터를 주고받아야 할 때에는(쓰레드 간 통신이 필요할 때에는) 힙 영역을 활용한다. 그럼 간단한 예제를 통해서 둘 이상의 쓰레드가 힙에 할당된 특정 메모리 영역에 함께 접근하는 예를 보이겠다.

class Sum
{
    int num;
    public Sum() { num = 0; } // 생성자
    public void addNum(int n) 
    {   
        synchronized(this)
        {
            num+=n;             
        }
    }
    public int getNum() { return num; }
}

class AdderThread extends Thread
{
    Sum sumInst;
    int start, end;

    public AdderThread(Sum sum, int s, int e)
    {
        sumInst = sum;
        start = s;
        end = e;
    }

    public void run() 
    {
        for(int i = start; i <= end; i++)
            sumInst.addNum(i);
    }
}

class ThreadHeapMultiAccess
{
    public static void main(String[] args)
    {
        /* 
            아래의 코드는 Sum 인스턴스의 참조 부분을 at1, at2에서 생성하는 쓰레드 인스턴스에 생성자를 통해서 전달한다.
        */
        Sum s= new Sum();
        AdderThread at1 = new AdderThread(s, 1, 50);    
        AdderThread at2 = new AdderThread(s, 51, 100);
 
        at1.start();
        at2.start();
        
        try {
            at1.join();
            at2.join();
        } catch (Exception e) {
            //TODO: handle exception
            e.printStackTrace();
        }
        System.out.println("1~100까지의 합 : "+s.getNum());
    }
}

 사실 위 예제는 메모리 구성을 모르는 상태에서도 이해할 수 있다. 하지만 이러한 코드 구성이 가능한 이유가 메모리 구성에 있다는 것을 알아 둘 필요는 있다. 만약에 쓰레드 별로, 스택과 마찬가지로 독립된 힙이 할당되었다면, 위 예제의 실행결과는 어떠했겠는가?위 예제에서 보였듯이 31행에서 생성한 인스턴스의 참조 값을 두 쓰레드에 전달하는 것은 가능하다. 단! 각각의 쓰레드가 이 참조 값을 이용해서 인스턴스에 접근할 때에는 문제가 생기게 된다. 쓰레드 자신에게 할당된 힙에서 인스턴스를 찾으려고 할 테니 말이다. 

 

* <<나는 정말 JAVA를 공부한 적이 없어요>>의 674-676p에 나와있는 내용입니다.