Media Log


1. 스레드(Thread)


스레드란 무엇일까요? 이 스레드(Thread)란 '프로그램의 실행 흐름, 프로그램을 구성하고 있는 실행 단위'라 말할 수 있습니다. 여태까지 보아왔던 프로그램들은 죄다 단일 스레드였으며, 자바는 멀티 스레드 기능을 지원합니다. 우리가 보아왔던 main 메소드 역시 처음부터 시작하여 모든 명령들이 순차적으로 실행되는 단일 스레드입니다. 만약에, 하나의 프로그램에서 작업을 분할하여 여러개의 작업을 동시에 수행하게 하고 싶을 경우에는 어떻게 해야 할까요? 여기선, 멀티 스레드(Multi-Thread)를 사용하시면 됩니다. 자, 이제 직접 스레드를 사용하여 스레드가 어떠한 특징들을 지니고 있는지 알아보도록 합시다.


class MultiThread extends Thread { // Thread 클래스를 상속

String name;


public MultiThread(String name) {

System.out.println(getName() + " 스레드가 생성되었습니다.");

this.name = name;

}

public void run() {

for(int i=0; i<50; i++) {

System.out.println(getName() + " (" + name + ")");

try {

sleep(100);

catch (InterruptedException e) { e.printStackTrace(); }

}

}

}


class TutorialThread

{

public static void main(String[] args)

{

MultiThread mt1 = new MultiThread("Thread1");

MultiThread mt2 = new MultiThread("Thread2");

MultiThread mt3 = new MultiThread("Thread3");


mt1.start();

mt2.start();

mt3.start();

}

}


결과:

Thread-0 스레드가 생성되었습니다.

Thread-1 스레드가 생성되었습니다.

Thread-2 스레드가 생성되었습니다.

Thread-0 (Thread1)

Thread-1 (Thread2)

Thread-2 (Thread3)

Thread-1 (Thread2)

Thread-0 (Thread1)

...

Thread-0 (Thread1)

Thread-1 (Thread2)

Thread-2 (Thread3)

Thread-0 (Thread1)


이 프로그램을 실행하면 여러개의 run 메소드가 동시에 실행되는 것을 보실 수 있습니다. 코드로 돌아가서, 1행에서 MultiThread 클래스에 Thread 클래스를 상속하였고, 이 클래스로부터 생성되는 객체는 스레드가 됩니다. 8행을 보시면 run 메소드가 보이시죠? 이 스레드를 실행하기 위해서는 run 메소드가 반드시 필요합니다. run 메소드 내에서는 스레드가 수행할 작업의 내용을 적어주시면 됩니다. 12행에서는 스레드를 시간 간격을 두고 실행하여 테스트하기 위해서 0.1초의 간격을 준것이며, 작업 중에 InterruptedException 예외(스레드가 오랫동안 대기중이거나, 대기상태가 된 스레드를 인터럽트 하는 경우에 발생하는 예외)가 발생하면 printStackTrace 메소드로 인해 에러가 발생한 원인을 집어주며 단계별로 에러를 출력하게 됩니다. 


이제 TutorialThread 내의 main 메소드 안을 봅시다. MultiThread 객체 mt1(Thread1), mt2(Thread2), mt3(Thread3)가 만들어지고 start 메소드를 사용해 각각의 개체에 대해 스레드를 실행시킵니다.(스레드는 run 메소드가 종료되면 알아서 소멸합니다.) 그리고 결과를 보아하니 스레드가  0부터 시작하여 2까지 순차적으로 실행되는게 아닌, 실행순서가 정해져 있지 않은 비동기적인 실행을 보이고 있습니다.


Thread.State 값 

설명 

NEW

시작되지 않은 상태

RUNNABLE

실행 가능 상태

WAITING

대기 상태

TIMED_WAITING 

스레드가 특정 시간동안 대기 상태

BLOCKED

스레드가 잠겨 있어 풀리기를 기다리는 상태 

TERMINATED

스레드가 종료된 상태


사실은, 스레드가 동시에 수행하는 것같지만 하나의 스레드만 CPU의 할당을 받아 실행할 수 있습니다. 그런데 어떻게, 동시에 수행되는 것처럼 보일까요? start 메소드로 인해 mt1, mt2, mt3은 실행 가능한 상태가 되었습니다. JVM은 스레드의 상태를 실행 가능한 상태와 실행 상태로 번갈아 가며 실행하여 동시에 수행되는 것과 같은 효과를 보이고 있습니다. 


2. Runnable 인터페이스


지금까지 Thread 클래스를 상속하여 스레드를 생성했지만, Runnable 인터페이스를 구현(implement)하여 스레드를 생성할 수 있습니다. 위에서 사용되었던 예제를 Runnable 인터페이스를 구현하여 재구현 하였습니다.


class MultiThread implements Runnable {

String name;


public MultiThread(String name) {

System.out.println(name + " 스레드가 생성되었습니다.");

this.name = name;

}

public void run() {

for(int i=0; i<50; i++) {

System.out.println(Thread.currentThread().getName() + " (" + name + ")");

try {

Thread.sleep(100);

} catch (InterruptedException e) { e.printStackTrace(); }

}

}

}


class TutorialThread

{

public static void main(String[] args)

{

MultiThread mt1 = new MultiThread("Thread1");

MultiThread mt2 = new MultiThread("Thread2");

MultiThread mt3 = new MultiThread("Thread3");

Thread tr1 = new Thread(mt1);

Thread tr2 = new Thread(mt2);

Thread tr3 = new Thread(mt3);


tr1.start();

tr2.start();

tr3.start();

}

}


결과:

Thread1 스레드가 생성되었습니다.

Thread2 스레드가 생성되었습니다.

Thread3 스레드가 생성되었습니다.

Thread-0 (Thread1)

Thread-1 (Thread2)

Thread-2 (Thread3)

...

Thread-2 (Thread3)

Thread-1 (Thread2)

Thread-0 (Thread1)


위 코드에서 바뀐걸 찾아보자면, 1행에서 Runnable 인터페이스를 구현하는것과 11행과 13행에서 Runnable 인터페이스는 Thread 클래스를 상속받지 못하므로 Thread 메소드를 이용하지 못하니 Thread 클래스에 접근하여 sleep 메소드와 getName 메소드를 사용합니다. 그리고 23~25행에서 MultiThread 클래스의 객체를 만듭니다. 하지만 이 객체를 대상으로 start 메소드를 사용할 수 없습니다. Runnable 인터페이스 내에는 단지 run() 멤버만을 가지기 때문입니다. start 메소드는 Thread 클래스의 메소드이므로 Thread 객체를 생성하고 매개변수로 위의 클래스의 레퍼런스를 넘겨줍니다.


유의하실 점은 Thread 클래스가 사용 방법이 더 간단하나, Thread 클래스를 상속하면 다른 클래스를 상속할 수 없다는 것만 명심하시면 됩니다.


3. 스레드의 우선 순위(Thread Priority)


스레드의 우선 순위에 따라 실행을 우선시 합니다. 즉, 우선 순위가 높은 스레드는 우선 순위가 자기보다 낮은 스레드 보다 더 많이 실행되는 것입니다. 만약 동일한 우선순위의 스레드가 둘 이상 존재할 경우 CPU의 할당시간을 분배후 실행합니다. 가장 높은 우선 순위는 10, 기본 우선 순위는 5, 가장 낮은 우선 순위는 1로, 보통 스레드의 우선 순위는 5입니다. 우리가 만약 어떤 스레드의 우선 순위를 변경하고 싶다면 setPriority 메소드를 사용하면 됩니다. 아래 예제는 setPriority 메소드를 사용하여 스레드의 우선순위를 변경하는 예제입니다.


class MultiThread extends Thread {

String name;


public MultiThread(String name) {

this.name = name;

}

public void run() {

for(int i=0; i<50; i++) {

System.out.println(name + " (우선 순위: " + getPriority() + ")");

}

}

}


class TutorialThread

{

public static void main(String[] args)

{

MultiThread mt1 = new MultiThread("Thread1");

MultiThread mt2 = new MultiThread("Thread2");

MultiThread mt3 = new MultiThread("Thread3");


mt1.setPriority(Thread.MIN_PRIORITY);

mt2.setPriority(Thread.NORM_PRIORITY);

mt3.setPriority(Thread.MAX_PRIORITY);

mt1.start();

mt2.start();

mt3.start();

}

}


결과:

Thread1 (우선 순위: 1)

Thread3 (우선 순위: 10)

Thread2 (우선 순위: 5)

Thread3 (우선 순위: 10)

Thread1 (우선 순위: 1)

Thread3 (우선 순위: 10)

Thread2 (우선 순위: 5)

Thread3 (우선 순위: 10)

Thread1 (우선 순위: 1)

Thread3 (우선 순위: 10)

Thread3 (우선 순위: 10)

Thread3 (우선 순위: 10)

...

Thread1 (우선 순위: 1)

Thread1 (우선 순위: 1)

Thread1 (우선 순위: 1)


코드에서 23~25행을 보시면 setPriority 메소드가 호출됬습니다. 여기서 괄호 안에 오는 MIN_PRIORITY(1), NORM_PRIORITY(5), MAX_PRIORITY(10)은 Thread 클래스 내에 선언된 상수입니다. 이것 말고도 우리가 직접 1부터 10까지의 숫자를 주어 우선순위를 정할 수 있습니다.


4. 스레드의 동기화(Thread Synchronization)


실제로 멀티 스레드 프로그래밍에서는 2개 이상의 스레드가 실행되면서 데이터를 서로 공유합니다. 이 경우에 데이터가 동에 갱신(Update)된다면 어떤 일이 일어날까요? 만약에, A와 B를 포함한 여러 스레드가 존재한다고 칩시다. A 스레드가 작업을 수행하다가 중요한 값을 갱신하고 있었다고 합시다. 그런데 갑자기 실행 순서가 B로 바뀌어 B가 새로운 작업을 다시 수행하면서 값을 갱신하다가 다른 스레드에게 실행 권한이 넘어가면, 데이터의 손실이 발생하여 심각한 문제를 불러올 것입니다. 아래 예제를 보세요.


class clsNumber { // 데이터 공유를 위한 클래스

int num = 0;

public void addNum() { num++; }

public int getNum() { return num; }

}


class MultiThread extends Thread {

clsNumber number;


public MultiThread(clsNumber cn) {

number = cn;

}

public void run() {

for(int i=0; i<50000; i++) {

number.addNum();

}

}

}


class TutorialThread

{

public static void main(String[] args)

{

clsNumber number = new clsNumber(); // 데이터 공유를 위한 클래스 객체 생성

MultiThread mt1 = new MultiThread(number);

MultiThread mt2 = new MultiThread(number);

MultiThread mt3 = new MultiThread(number);


mt1.start();

mt2.start();

mt3.start();


try {

mt1.join(); // mt1 스레드 수행 후 mt2를 수행한다. 

// join(): 다른 스레드가 하는 일이 마무리 될때까지 기다린다!

mt2.join();

mt3.join();

} catch (Exception e) { e.printStackTrace(); }


System.out.println(number.getNum());

}

}


결과:

115928


코드를 보시면, 1~5행에서는 데이터 공유를 위한 클래스가 정의되어 있습니다. num 멤버변수의 값은 0으로 초기화 되었으며, 스레드가 실행되면 데이터 공유 클래스의 객체에 저장된 num의 값을 1 증가시킵니다. 코드만 놓고 봐서는, 3개의 스레드에 의해 150000이 출력되어야 합니다. 그런데 결과는 115928을 출력했습니다. 이유는 3행의 addNum 메소드 내에 있는 num++; 때문에 그렇습니다. 하나의 스레드가 이 문장을 실행시키다가, 실행 권한이 다른 스레드에게로 넘어가 동시에 이 문장을 실행합니다. 이러한 문제를 해결하기 위해서는 둘 이상의 스레드에 의해 동시에 실행되지 않게 하면 됩니다. 이것을 자바에서는, 동기화(synchronized) 키워드를 이용하여 동기화 영역을 지정합니다. 이 키워드를 사용하여 예제를 수정해보았습니다.


class clsNumber {

int num = 0;

public synchronized void addNum() { num++; } // 동기화 메소드, 동시실행 방지!

public int getNum() { return num; }

}

...


결과:

150000


이제는 정상적으로 결과가 출력됨을 보실 수 있습니다. 하지만 동기화 메소드 방법은 메소드 내에서 수행할 내용이 많을 경우에는 스레드의 효율이 떨어질 수 있어, 코드 일부를 동기화 대상으로 두기 위해 '동기화 객체'를 사용할 수 있습니다. 아래는 일부 영역만 동기화 영역으로 구성하는 예입니다.


class clsNumber {

int num = 0;

public void addNum() {

synchronized(this) { // 이부분만 동기화 영역!

num++; 

}

...

}

...


오늘의 스레드 강좌는 여기서 마치도록 하겠습니다. 수고하셨습니다. 부족한 설명이 있다 싶으시면 덧글로 달아주시면 보충 설명을 덧붙이도록 하겠습니다.


다음 강좌는 파일 입출력(File Input/Output) 입니다.

저작자 표시
신고
  1. 김승현 at 2012.10.19 23:10 신고 [edit/del]

    감사합니다~ 어렵네영~

    Reply
  2. answer at 2013.12.24 13:46 신고 [edit/del]

    아 이제 본격적으로 어려워 지네요;; 좋은강의 감사합니다

    Reply
  3. 김송이 at 2014.08.13 14:13 신고 [edit/del]

    정성있는 강의 정말 감사합니다. 제가 여기까지 따라오다니 기적입니다.

    Reply
  4. 지나가던행인 at 2015.11.25 20:33 신고 [edit/del]

    Thread를 상속받을 때 import를 안해줘도 되는건가요? Thread라는 클래스는 어디서 나오는건가요?

    Reply
    • BlogIcon EXYNOA at 2015.11.25 21:15 신고 [edit/del]

      아래 링크를 참고하시기 바랍니다. java.lang은 참고로 19편에서 언급한 바와 같이 자동으로 import되는 패키지입니다.
      http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html

  5. 행인 at 2016.11.01 19:12 신고 [edit/del]

    안녕하세요 잘보고 갑니다 감사합니당!

    Reply
  6. 행인2 at 2017.02.06 10:58 신고 [edit/del]

    감사히 읽었습니다~

    Reply
  7. ㅁㄴㅇ at 2017.05.07 20:57 신고 [edit/del]

    존경합니다 이렇게 설명을쉽게 잘해주시다니

    Reply
  8. 행인33 at 2017.06.26 13:26 신고 [edit/del]

    스레드에 대해 정말 제대로 이해하고갑니다. 좋은 설명 감사합니다

    Reply

submit

티스토리 툴바