ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Camel][Java] Thread (쓰레드)
    Java/개념 정리 2020. 3. 2. 14:21

    Thread(쓰레드)란?

    쓰레드에 대해 설명하기에 앞서 쓰레드를 이해하기 위해 필요한 내용을 설명하겠습니다.

     우선 프로그램을 실행하라는 요청을 사용자가 보내면 프로그램을 실행하는 것은 운영체제에 의해 이루어집니다. 사용자가 프로그램의 실행을 요청하면 메모리 공간이 할당되며, 이 메모리 공간을 기반으로 실행 중 에 있는 프로그램을 우리는 '프로세스'라고 합니다. 즉, 쉽게말하면 프로세스는 '실행중인 프로그램'이라고도 할 수 있습니다. 근데 이러한 프로세스 내에서 하나의 프로그램의 흐름만 있을 수 있는 것이 아니라, 둘 이상의 프로그램의 흐름이 있을 수 있습니다. 

     쓰레드라는 것은 이런 프로세스 내부에서 프로그램의 흐름을 형성하는 것입니다. 즉, 정리해서 말하면 하나의 프로세스 내에는 여러개의 쓰레드가 존재할 수 있다는 것입니다.

     

     

    Thread의 생성

    Thread 클래스의 상속을 통한 생성

     Java에서 쓰레드를 사용하기 위해서는 Thread 클래스를 상속하는 클래스를 정의해줘야합니다. 그리고 쓰레드 또한 인스턴스로 표현을 합니다. 쓰레드의 사용 예시는 아래의 코드를 확인하시길 바랍니다.

    class ThreadInfo extends Thread{ // Thread 표현을 위한 클래스 정의 & Thread 클래스 상속
    	String tName;
        
        	public ThreadInfo ( String name ) { tName = name; }
            
        	// run 메소드는 Thread의 main 메소드라고 생각하면 된다. Thread 클래스에서 오버라이딩 한 것이다.
            public void run () {   
        		for(int i=0; i<100; i++) {
            		System.out.println("Thread Name is "+ tName);
                		
                        	// sleep 메소드는 쓰레드의 실행 흐름을 일시적으로 멈추는 메소드이다.
                        	// 인자에 1000을 입력하면 1초간 멈추게 된다. 
                        	try { sleep(1000); } 
                        	catch( InterruptedException e ) {
                			e.printStackTrace();
                		}
            	}
        	}
    }
    
    class ThreadExample {
    	public static void main ( String[] args ) {
        	ThreadInfo tI1 = new ThreadInfo("Camel Thread"); // Camel Thread 생성
            ThreadInfo tI2 = new ThreadInfo("Pig Thread");  // Pig Thread 생성
            tI1.start();  // Camel Thraed를 통해 흐름 형성
            tI2.start();  // Pig Thraed를 통해 흐름 형성
        }
    }

     위의 코드를 실행하면 2개의 쓰레드 인스턴스의 run 메소드가 동시에 실행되는 것을 확인할 수 있습니다. 이것은 main 메소드라는 실행흐름 이외의 새로운 2개의 실행흐름이 형성된 것입니다. 코드에 대한 설명은 주석을 통해 설명했습니다.

     

     하지만 여기서 우리는 궁금한 점이 한두가지가 아닙니다. 첫 번째 궁금한 점으로, 왜 start 메소드를 호출해서 run 메소드를 실행할까? 그냥 바로 run 메소드를 호출하면 어떻게되지? 일단 run 메소드를 직접 호출하는 것이 불가능한 것은 아닙니다. 하지만 run 메소드를 직접 호출하게되면 이는 단순 메소드의 호출일뿐 쓰레드의 생성이 되지 않습니다. JVM에서는 start 메소드의 호출을 통해 메모리 공간의 할당과 같은 쓰레드 실행을 위한 기반을 마련한 후 run 메소드를 대신 호출해주는 것을 요구합니다. 간단한 예로는 우리가 main 메소드를 직접 호출하지 않는 것과 같습니다. 

     

     두 번째 궁금한 점은, main 메소드가 run 메소드보다 먼저 종료되면 어쩌나요? 결론은 마지막까지 남은 쓰레드의 실행을 완료해야 프로그램이 종료된다는 것입니다. 여기서 한가지 더 알아야 할 점은 main 메소드도 쓰레드에 의해 실행된다는 것입니다. 이런 main 메소드를 실행하는 쓰레드를 우리는 'main 쓰레드'라고 합니다. 

     

     세 번째로는, 만약 다른 클래스를 상속해야해서 Thread 클래스를 상속할 수 없는 상황이라면 어떻게 해야할까? 이런 상황은 충분히 발생할 수 있는 상황입니다. 이런 문제점을 해결하기 위해 Java에서는 인터페이스 구현을 통한 방법을 통해이 문제를 해결할 수 있습니다. 아래의 코드를 통해 인터페이스 구현을 통한 쓰레드 생성방법을 보이겠습니다. 

     

    인터페이스 구현을 통한 Thraed 생성

    class Adder {
    	int num;
        	public Adder() { num=0; }
        	public void addNum( int n ) { num+=n; }
        	public int getNum(){ return num; }
    }
    
    //Runnable 인터페이스는 run 메소드로만 이루어져 있다.
    class CalcThread extends Adder implements Runnable { 
    	int fisrtNum;
        	int lastNum;
        
        	public CalcThread ( int f, int l ) { 
            	firstNum = f;
                	lastNum = l;
            }
            
        	public void run () {   
        		for(int i=fristNum; i<=lastNum; i++) {
            		addNum(i);
            	}
        	}
    }
    
    class RunnableThreadExample {
    	public static void main ( String[] args ) {
        	
            // AdderThread 클래스는 Thread 클래스를 상속하지 않기 때문에 start 메소드를 호출할 수 없다.
            AdderThread aT1 = new AdderThread(1, 10); 
            AdderThread aT2 = new AdderThread(10, 100);
            
            // Thread 클래스에는 Runnable 인터페이스를 구현하는 인스턴스의 참조값을 전달 받을 수 있는 생성자가 존재한다.
            Thread thd1 = new Thread(aT1);
            Thread thd2 = new Thread(aT2);
            aT1.start();  
            aT2.start();
            
            try {
            	// join 메소드를 호출함으로써 해당 쓰레드가 종료되어야 아래의 출력문을 실행한다.
            	thd1.join();
                	thd2.join();
            } catch ( InterruptedException e ) {
            	e.printStackTrace();
            }
            
            // 1부터 100 까지의 합이 출력된다.
            System.out.println("Total Sum = "+(aT1.getNum()+aT2.getNum()));
        }
    }

     

     

    Thread의 특징

     앞서 설명했듯이 상황에 따라 다수의 쓰레드의 생성이 가능합니다. 이렇게 다수의 쓰레드를 생성하게 되면 JVM 내부의 쓰레드스케줄러는 쓰레드의 실행을 컨트롤하는 '스케줄링'을 해야합니다. 스케줄링에 과정에서는 우선순위가 높은 쓰레드의 실행이 우선이며, 만약 동일한 우선순위가 존재한다면 CPU의 할당시간을 분배해서 실행합니다. 이때 우선순위는 정수 1부터 10까지로 나타냅니다. 우선순위가 10이라는 것은 우선순위가 가장 높은 것이고, 1이면 가장 낮은 우선순위를 말합니다. 우선순위를 알고 싶다면 Thread 클래스의 getPriority 메소드를 호출하면 됩니다.  

     이런 우선순위를 기반으로한 스케줄링으로 인해 대부분의 시스템에서는 우선순위가 높은 프로그램에게만 실행기회를 제공합니다. 이러한 특징으로 인해 우선순위가 낮다면 언제 실행될지 알 수 없다고 생각할 수 있습니다. 하지만 우선순위가 높은 쓰레드가 있을지라도 CPU의 할당이 필요치 않는 경우가 발생하면 그 쓰레드는 자신에게 할당된 CPU를 다른 쓰레드에게 넘겨주게 됩니다. 이로인해 우선순위가 낮은 쓰레드라도 실행 기회를 얻을 수 있는 것입니다. 

     

     쓰레드간 통신을 통해 데이터를 주고 받아야할 때에는 힙 영역을 활용합니다. JVM의 구조를 확인해 보면 모든 쓰레드는 각자 자신의 스택을 할당 받습니다. 하지만, 힙 영역과 메소드 영역은 모든 쓰레드가 공유하기 때문에 힙 영역을 활용해 쓰레드간 통신을 하는 것입니다. JVM의 구조에 대해 기억이 나지않거다 다시 확인하고 싶으신 분은 JVM의 구조에 대한 포스팅을 참고하시길 바랍니다. 2020/02/27 - [Java] - [Camel][Java] JVM(가상머신)의 메모리 모델

     

    요약

    - Thread는 우선순위를 기반으로 스케줄링된다. 

    - 우선순위가 높은 Thread가 먼저 실행된다.

    - 가장 높은 우선순위를 나타내는 정수는 10이고 가장 낮은 우선순위는 1로 나타낸다.

    - 우선순위가 높더라도 CPU할당이 필요하지 않은 경우 할당된 메모리를 다른 Thread에게 양보한다.

    - 쓰레드간 통신에는 힙 영역이 활용된다. 

     

     

    Thread의 생명주기

     쓰레드가 생성되면 쓰레드는 New, Runnable, Blocked, Dead 총 4가지 상태 중 한가지 상태에 있게됩니다. 

    New 상태

     이 상태는 쓰레드가 new 키워드를 통해 인스턴스화 된 상태를 가리킵니다. 하지만 이 상태는 아직 JVM에 의해 관리되는 상태가 아니고, 그렇기에 운영체제 입장에서는 아직 Thread라고 하기 힘들 수 있는 상태입니다.

    Runnable & Running 상태

     쓰레드 인스턴스를 대상으로 start 메소드가 호출되면 Runnable 상태에 돌입하게 됩니다. 이 상태는 실행을 위한 준비가 끝난 단계이며 스케줄러를 기다리는 상태입니다. 스케줄러에 의해 실행되게되면 비로소 run 메소드가 호출되게 됩니다. 

    Blocked 상태

     실행 중인 쓰레드가 CPU의 할당이 필요하지 않은 입출력 연산을 하게되거나 sleep, join 메소드를 호출하면 Blocked 상태가 됩니다. 이 상태에서는 스케줄러에 의해 실행될 수 없고 실행되기 위해서는 Blocked 상태에 놓인 원인을 제거해 Runnable 상태로 돌아가야합니다.

    Dead 상태 

     run 메소드으 실행이 완료되어 run 메소드를 빠져나오게 되면 해당 쓰레드는 Dead 상태가 됩니다. 이 상태의 쓰레드는 할당 받았던 메모리와 쓰레드 관련 정보가 사라지게 됩니다. 한번 Dead 상태에 접어들었다면 다시는 Runnable 상태로 돌아갈 수 없습니다. 

     

     

    댓글

Camel`s Tistory.