티스토리 뷰

Java/Class

12. Thread : 쓰레드

알 수 없는 사용자 2018. 11. 14. 02:08


이번 포스팅에서는

쓰레드에 대해서 알아보도록 하겠습니다.

저는 쓰레드를 알아가면서

프로그래밍에 한 발짝 더 다가간 

느낌이 들었습니다.


====================================================


Thread : 쓰레드





1. Thread : What?


 처음 Thread를 접하면 단어 자체가 어렵다고 느껴질 수도 있습니다. 하지만 Thread가 무엇이고 어떨 때 사용되는지 이해하는 순간 좀 더 멋진(?) 프로그래밍을 할 수 있고 더욱 흥미를 느끼실 것입니다.

 

 쓰레드란 '프로세스 내에서 실행되는 세부 작업의 단위'입니다. 그렇다면 프로세스는 무엇일까요?

 프로세스는 '실행 중인 프로그램'이라고 말할 수 있습니다. 우리가 프로그램을 실행하면 OS로부터 실행에 필요한 자원을 할당받아 프로세스가 되는 것이죠.

 시스템의 작업관리자를 들어가면 하나하나의 프로그램들이 실행되고 있는 상태를 볼 수 있는데 이때 나타나는 것이 프로세스입니다. 


 즉, 쓰레드는 '프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원울 이용해서 실제로 작업을 수행하는 것'이라고 할 수 있습니다.


[프로세스와 싱글쓰레드]


 위 사진처럼 쓰레드는 프로세스에서 일하는 하나의 일꾼처럼 표현할 수 있습니다. 

즉, 프로세스에는 무조건 하나 이상의 쓰레드가 존재해야 한다는 것이죠.

그렇다면 우리가 앞서 봐왔던 예제들에서는 어떻게 작동해왔던 것일까요?


우리는 앞서 싱글쓰레드 즉, 하나의 쓰레드를 이용해서만 프로그래밍을 했던 것입니다.

자바는 어떤 프로그램을 만들든지 기본적으로 하나의 쓰레드를 제공합니다. 

그렇기 때문에 기본 main() 메서드에서 수행하기위해 쓰레드가 하나의 쓰레드가 사용되는데 이 쓰레드를 main 쓰레드 혹은 데몬 쓰레드라고도 부릅니다.





2. Thread : Multi Thread?


기본적으로 제공되는 main 쓰레드외에 추가로 쓰레드를 더 생성하여 사용한다면 멀티 쓰레드로 사용할 수 있습니다.


[프로세스와 멀티쓰레드]


 멀티 쓰레드는 '하나의 프로세스에 여러 일꾼이 동시에 작업하는 것'입니다.

 여러 일꾼이 동시에 작업한다는 것을 코드에 빗대어 이해하려 한다면 잘 안될 수도 있습니다.


 예로 들어보겠습니다.

우리가 묵찌빠 게임을 만들려고 합니다. 상대방과 나의 공격순서를 정하고 유저가 내는 수를 비교하여 승패만 가려주면 되는 아주 단순한 게임이 될 것입니다.

이렇게 게임을 만들고 보니 공격자가 공격을 진행하지 않고 무한정 대기하는 경우가 생겨 5초안에 내지 않으면 지도록 프로그램을 설계하려 합니다.


 이런 경우 어떻게 해야 할까요?

만약 싱글쓰레드로 처리한다 하면 사용자가 낼 값을 받기 위해 기다리는 동작과 카운트 다운을 하는 동작을 동시에 진행해야 합니다.

마치 일꾼(쓰레드)오른손과 왼손이 다른 작업을 하는 것처럼 말이죠.


하지만 기본적으로 싱글쓰레드에서는 정해진 흐름에 따라 프로그램이 작동하게 됩니다. 때문에 데몬쓰레드인 main 쓰레드가 게임의 전체적인 흐름을 제어하고 있으면서 경우에 따라 카운트 다운까지 해야 하는 상황이 오면 곤란하게 됩니다. 


 이럴 때 멀티쓰레드를 사용하는 것입니다. 사용자가 낼 값을 받기 위한 작업은 main 쓰레드가 한다면 카운트 다운을 하는 별도의 쓰레드를 생성하는 것입니다.



[멀티쓰레드를 이용한 묵찌빠 게임]



이렇게 된다면 2명의 일꾼을 가지고 묵찌빠 게임을 동작하는 것이라 할 수 있고, 또한 한 개의 main 문 안에서 별도의 main 문이 또 실행된다는 느낌도 받을 수 있을 것입니다.


 멀티쓰레드는 일꾼이 여러 명이라는 장점 때문에 프로그램의 속도가 빠르리라 생각할 수 있어 쓰레드가 많을수록 좋다고 생각할 수 있습니다.

 하지만 싱글 코어에서는 실제로 멀티쓰레드는 쓰레드가 동시에 실행된다기보다는 빠른 속도로 다수의 쓰레드를 전환하면서 사용자가 보일 때는 동시에 실행되는 것처럼 작동하는 것입니다.

 그렇기 때문에 하나의 쓰레드를 통해 작업할 수 있는 것을 멀티 쓰레드로 작업한다면 쓰레드 간의 전환 시간으로 인해 싱글쓰레드보다 멀티쓰레드가 느려지는 현상이 발생합니다. 


 장점

 단점

 - CPU의 사용률 향상

- 자원을 효율적으로 사용

- 사용자에 대한 응답성 향상

- 작업 분리에 의한 코드 간결

- 작업 속도 향상(멀티코어)

 - 여러 쓰레드가 프로세스 내에서 같은 자원을 

공유하며 생기는 동기화, 교착상태

- 쓰레드를 전환하면서 생기는 대기시간(싱글코어)





3. Thread : How?


 쓰레드를 사용할 수 있는 방법은 크게 3가지로 볼 수 있습니다.



1) extends Thread


 Thread 클래스를 상속하여 클래스를 설계하는 방법입니다.

Thread 클래스에는 run()이라는 메소드가 정의되어 있고 이 메소드를 오버라이드하는 것을 통해 별도의 작업을 정의할 수 있습니다.

run()이 main 메소드와 같은 부분이라 생각하면 편합니다.

  

class CharPrint extends Thread{
char start;
char end;
public CharPrint(char s, char e) {
start = s;
end = e;
}

@Override
public void run() {
for(char c = start; c<end;c++) {
System.out.print(c);
}
System.out.println();
}
}

public class Source02_Thread{
public static void main(String[] args) {
Thread t = new CharPrint('', '');
t.start();
Thread t2 = new CharPrint('', '');
t2.start();
for(int cnt=1; cnt<=10;cnt++) {
System.out.println("---" + cnt);
}
}
}


 위의 코드는 해당 문자열 사이의 모든 문자를 찍는 CharPrint를 설계하고 이를 Thread를 통해서 동작시킨 것입니다.

run() 메소드 안에는 동작할 행동을 정의하고 다른 객체들과 마찬가지로 Thread 변수에 CharPrint 인스턴스를 생성하여 사용하면 됩니다.




2) implements Runnable


 다음은 Runnable 인터페이스를 구현하여 사용하는 방법입니다. 이 방법은 인터페이스를 구현하는 것으로 재사용성이 높고 extends 하지 않기 때문에 다른 클래스를 상속할 수 있다는 점에서 자주 사용되는 방법입니다.


class CharPrint implements Runnable{
char start;
char end;
public CharPrint(char s, char e) {
start = s;
end = e;
}

@Override
public void run() {
for(char c = start; c<end;c++) {
System.out.print(c);

}
System.out.println();
}
}

public class Source02_Thread{
public static void main(String[] args) {
CharPrint c1 = new CharPrint('', '');
CharPrint c2 = new CharPrint('', '');
Thread t = new Thread(c1);
t.start();
Thread t2 = new Thread(c2);
t2.start();
for(int cnt=1; cnt<=10;cnt++) {
System.out.println("---" + cnt);
}
}
}


  Thread를 상속한 것과 달리 Runnable 인터페이스를 구현한 클래스는 따로 클래스를 생성하여 Thread 객체 생성 시 인자로 넘겨주어야 합니다.




3) anonymous Class


 3번째 방법은 익명 클래스입니다. 익명 클래스는 재사용성 없이 한 번만 사용될 때 사용되는 방법입니다.

new Thread() {
@Override
public void run() {
for(int cnt=1; cnt<=1000;cnt++) {
System.out.println("!!!!");
}
}
}.start();

다음과 같이 Thread를 생성하면서 이름이 없는 클래스로 정의하여 바로 사용하기 때문에 재사용이 불가능합니다.





4. Thread : Method?


[Thread 클래스에 정의된 메소드]



Thread 클래스에는 많은 기능의 메소드가 정의되어 있지만 몇 개의 메소드만 정확히 알아도 무방하다고 생각합니다.


- sleep() : 지정된 시간 동안 해당 쓰레드를 일시 정지시키는 메소드입니다.

- join() : 지정 시간 동안 쓰레드가 실행되고 끝나면 join() 메소드를 실행시킨 쓰레드로 돌아가 작업합니다.

- interrupt() : sleep()이나 join()에 의해 일시 정지 상태인 쓰레드를 싱행대기상태로 만드는 메소드입니다.

- stop() : 쓰레드를 즉시 종료시킵니다.

- suspend() : 쓰레드를 일시정지시킵니다.

- resume() : 일시 정지인 쓰레드를 실행 대기 상태로 만듭니다.

- yield() : 실행 중에 자신에게 주어진 실행 시간을 다른 쓰레드에게 양보합니다.

- setPriority() : 쓰레드의 우선순위를 지정한 값으로 변경합니다.

- currentThread() : 현재 실행 중인 쓰레드를 반환합니다.

- getName() : 쓰레드의 이름을 반환합니다.

- start() : 쓰레드를 시작시킵니다.


import javax.swing.JOptionPane;

class Worker extends Thread{
@Override
public void run() {
for(int cnt = 1; cnt<=1000000; cnt++) {
boolean t = this.isInterrupted();
System.out.println(cnt + " / do working.. / " + t);
if(t) break;
}
}
}

public class Source05_Thread {
public static void main(String[] args) {
System.out.println("[Main] start");
Worker w = new Worker();
w.setDaemon(true); // 자신을 제외하고 가동중인 쓰레드가 없다면 알아서 멈춤.
w.start();
try {
for(;;) {
String cmd = JOptionPane.showInputDialog("controll?");
switch(cmd) {
case "sleep":
Thread.sleep(3000); // 해당 초만큼 일시정지
break;
case "start":
w.start(); break;
case "interrupt":
w.interrupt(); break;
case "join":
w.join(2000); // 해당 쓰레드의 작업을 2초동안 끝나길 기다린다. 현재 쓰레드는 일시정지
break;
case "suspend":
w.suspend(); // 쓰레드를 일시정지시킴
break;
case "resume":
w.resume(); // 정지된 쓰레드를 다시 가동
break;
}
}
}catch(InterruptedException e){
System.out.println("InterruptedException..");
}finally {
// w.stop();
}

}
}





5. Thread : synchronized?

 

 멀티쓰레드에서는 프로세스 내의 자원을 공유하기 때문에 서로 작업에 영향을 줄 수 있습니다.

예를 들어, 두 개의 쓰레드가 하나의 변수를 참조하는 동시에 변수에 값을 저장하면서 작업하고 있다면 어떻게 될까요?

1번 쓰레드가 작업하던 도중에 2번 쓰레드에게 제어권이 넘어갔을 때, 1번 쓰레드가 작업하던 공유데이터를 2번 쓰레드가 임의로 변경하였다면, 다시 1번 쓰레드가 제어권을 받아서 나머지 작업을 마쳤을 때 의도한 것과 다른 결과를 얻을 수 있습니다. 

 이는 쓰레드가 동시에 변수에 접근하기 때문에 생기는 현상으로 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 동기화라고 합니다.



1) 메서드 전체를 임계 영역으로 지정

public synchronized void test() {
System.out.println("Hellow World");
}

위와 같이 메소드에 synchronized 키워드를 사용한다면 이 메소드에 한 쓰레드가 접근해있을 때 다른 쓰레드가 메소드에 접근하지 못하도록 합니다.



2) 특정 영역을 임계 영역으로 지정

public void test() {
synchronized(this) {
System.out.println("Hellow World");
}
}

다음과 같이 코드 일부를 synchronized 키워드로 감싼다면 해당 블럭의 영역 안드로 들어가면서 들어간 쓰레드는 지정된 객체의 제어권을 얻게 되고 다른 쓰레드는 기다리게 되는 것입니다.

'Java > Class' 카테고리의 다른 글

13. Networking : UDP,TCP  (1) 2018.11.16
11. Collections Framework : 객체 비교  (0) 2018.11.04
10. Collections Framework : Map  (0) 2018.10.31
9. Collections Framework : Queue, Deque  (0) 2018.10.30
8. Collections Framework : List  (0) 2018.10.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함