1. 스레드 이해하기!
스레드에 대해 이해하기 전에 먼저 짚고 넘어가야 할 개념이 있다.
- 바로 프로세스(process).
프로세스란 프로그램이 실행 중이다, 라는 뜻이다.
프로그램이 실행되고 있다면 어떤 상황이 벌어지고 있는 것일까?
그 프로그램이 데이터, 메모리등의 자원을 차지하고 사용하고 있다는 뜻일 것이다.
그리고 스레드란 프로세스 내에서 실제로 작업을 수행하는 주체이자 하나의 단위이다.
작업을 수행하는 주체라는 말은 프로그램 코드를 한 줄씩 실행하는 역할을 한다는 뜻이다.
이렇게 이해해보면 어떨까?
과거 프로그램은 원래 한 가지의 일만 수행할 수 있는 것이 기본적인 프로그래밍의 개념이었을 것이다.
하지만 프로그램이 여러 가지의 일을 동시에 수행시키고 싶다면?
내가 메모장을 동시에 두 개를 켜서 작업을 한다면? 메모장이라는 하나의 프로그램이 동시에 전혀 다른 여러 문장을 저장한다면?
이 때 등장한 개념이 바로 프로세스의 스레드이다.
Thread는 사전적 의미로 한 가닥의 실이란 뜻이다.
한 가지의 작업을 실행하기 위해 순차적으로 실행할 코드를 실처럼 이어놓았다고 해서 유래된 이름이다.
"누구는 멀티를 잘해"라고 말할 때 이 멀티 태스킹이란 이야기를 들어본 적 있을 것이다.
프로세스도 당연히 멀티 프로세스가 존재한다.
하지만 멀티 태스킹이 꼭 멀티 프로세스를 의미하는 것은 아니다.
하나의 프로세스 내에서도 멀티 태스킹이 가능하기 때문이다.
꼭 여러 개의 프로세스가 존재해야만 멀티 태스킹이 가능한 것이 아니다.
이것을 가능하게 하는 것이 바로 스레드, 그리고 이런 경우에는 멀티 스레드(Multi-Thread)라고 부르는 것이다.
이를 좀 더 실생활적인 예를 들어보자.
누군가가 밥을 먹는다.
그리고 식사를 하면서 유튜브로 영상을 시청하고 있다.
그리고 마주앉은 사람과 대화를 한다.
이 모습을 통칭해서 우린 식사를 한다고 표현을 한다.
이 모습을 프로그램으로 표현하자면 우린 '사람'이라는 프로그램이 '식사를 한다'라는 프로세스를 수행하고 있다고 할 수 있으며 이 프로세스가 수행될 때 각자의 활동 하나 하나가 스레드로 작동한다고 생각하면 이해가 되는 것 같다.
프로그램의 모든 프로세스에는 반드시 한 개 이상의 스레드가 존재하며 두 개 이상의 스레드가 존재할 시 여러 작업을 동시해 수행하는 것이 가능하다.
자바에서는 이런 스레드를 언어 차원에서 지원을 한다.
이로 인해 안전성과 효율성이 보장이 된다.
2. 스레드가 있고!, 없고? 비교하기
스레드는 하나의 프로그램이나 하나의 메소드가 cpu자원을 전부 점유하는 것을 막을 수 있다.
그러니까 스레드가 없다는(1개 뿐이라는) 것은 한 프로그램, 프로세스, 메소드가 끝나지 않으면 다른 것은 절대로 실행되지 않는다는 뜻이다.
그래서 스레드가 적용되는 대표적인 모습이 바로 실시간 댓글창이다.
만약 실시간 댓글창에 멀티-스레드가 작동되지 않는다면, 내가 1번째 순서로 댓글을 작성하는 사람이고 내가 댓글을 작성하지 않는 한 어느 누구도 실시간으로 댓글을 달 수 없게 되어버린다(실시간이란 말의 의미가 무색해진다).
이제 이를 코드로도 살펴보려 한다.
1) 스레드를 사용하지 않는 경우
스레드를 사용하지 않는 경우를 확인해보고자 아래와 같은 클래스를 하나 만들었다.
class MyThread1 {
private int num;
private String name;
public MyThread1() {}
public MyThread1(int num, String name) {
this.num = num;
this.name = name;
} // end
public void start() {
run();
} // start() end
public void run() {
for(int a=0; a<num; a++) {
System.out.println(name + ":" + a);
} // for end
} // run() end
} // class end
그리고 아래와 같이 출력되게 했다.
MyThread1 t1 = new MyThread1(1000, "★");
MyThread1 t2 = new MyThread1(1000, "★★");
MyThread1 t3 = new MyThread1(1000, "★★★");
t1.start();
t2.start();
t3.start();
어떻게 될까?
t3.start()는 t1 / t2의 start() 함수가 다 실행될 때까지, 그러니까 2000번 실행될 때까지 기다려야하는 것이다.
이것이 멀티스레드가 없는 경우 벌어지는 일이다.
2) 스레드를 사용하는 경우(Class Thread)
그렇다면 멀티 스레드를 사용해보자!
스레드가 구현이 되는 과정은 JVM(자바 가상 머신)이 스레드 관리자에 등록하고, start() 메소드가 run()을 호출하는 과정이다. 위에서 말했듯 채팅, 또는 실시간 예매 등에 많이 사용된다.
스레드를 사용하기 위해선 스레드를 사용하려는 클래스에 Thread 클래스와 상속관계를 선언을 해주어야 한다.
그런데 그 클래스가 이미 부모가 있는 클래스라면?
그럴 땐 인터페이스(implements Runnable)를 사용한다.
만약 이 개념을 아직 모른다면 아래의 게시글을 살펴보아도 좋다.
참조 : https://ddcloud.tistory.com/129
그리고 Thread 클래스의 메서드 중 하나인 run() 함수를 위처럼 Override하여 불러온다.
(사실 위에서 구현한 모습이 Thread의 기본적인 동작 흐름을 코드로 구현한 것이다)
* start() 함수는 Thread 클래스에 상속되었기 때문이기도 하고 단지 run()함수를 호출하는 기능을 갖고 있기 때문에 굳이 Override하지 않고 사용한다.
클래스에 대한 코드는 아래와 같다.
class MyThread2 extends Thread {
private int num;
private String name;
public MyThread2() {}
public MyThread2(int num, String name) {
this.num = num;
this.name = name;
} // end
// start( )함수는 run() 함수를 호출하는 기능
@Override
public void run() { // 비지니스 로직 구현
for(int a=0; a<num; a++) {
System.out.println(name + ":" + a);
} // for end
} // run() end
} // class end
그리고 이 클래스를 출력시켰다.
MyThread2 t1 = new MyThread2(1000, "★");
MyThread2 t2 = new MyThread2(1000, "★★");
MyThread2 t3 = new MyThread2(1000, "★★★");
t1.start();
t2.start();
t3.start();
출력된 결과를 살펴보면 세 코드가 동시에 실행되고 있는 모습을 확인할 수 있다.
이것이 멀티-스레드를 동작시킨 모습이다.
3) 스레드를 사용하는 경우(Interface Runnable)
이번엔 클래스가 아니라 인터페이스를 활용하여 멀티 스레드를 사용해보자!
class MyThread3 implements Runnable {
private int num;
private String name;
public MyThread3() {}
public MyThread3(int num, String name) {
this.num = num;
this.name = name;
} // end
@Override
public void run() { // 비지니스 로직 구현
for(int a=0; a<num; a++) {
System.out.println(name + ":" + a);
} // for end
} // run() end
} // class end
Thread t1 = new Thread(new MyThread3(1000, "★"));
Thread t2 = new Thread(new MyThread3(1000, "★★"));
Thread t3 = new Thread(new MyThread3(1000, "★★★"));
t1.start();
t2.start();
t3.start();
이번엔 MyThread3 클래스를 만들고 Runnable 인터페이스를 implements 시켰다.
그리고 여기서 스레드를 사용하기 위해선 Runnable 인터페이스의 run() 메서드를 활용하기 위해 Thread 클래스에 MyThread3 클래스를 대입시켜 준다.
이것이 이해가 좀 어려웠는데, 스스로 정리한 것을 글로 풀어보자면!!
인터페이스는 상속보단 구현의 개념이 강하다.
이는 인터페이스가 추상의 개념인 것을 생각해보면 더더욱 맞는 것 같다(지나가던 시민분이 내 이해가 틀린 것을 발견해주신다면 알려주시면 좋겠다).
그렇기에 Runnable 인터페이스에서 run() 메서드를 호출하기 위해선 클래스인 Thread를 불러와야한다.
그래서 MyThread3 클래스를 Thread 클래스에 대입하는 것이다.
4) start()와 run() 의 차이
호기심이 들어서 t1.run()을 동작시켜보았다.
오~ 잘 된다.
그리고 t2.run()도 함께 동작시키자 이상한 일이 벌어진다.
단일 스레드로 코드가 동작하는 것이다.
왜일까?
run()은 단순하게 Runnable 인터페이스의 메서드를 호출시킨다.
그래서 Thread 클래스로 넘어가지 않게 되고, 따라서 스레드를 생성하지 않는다.
반면
start()는 자바의 Thread 클래스의 메서드이며 Runnable 인터페이스의 run() 메서드를 호출시키는 기능을 한다.
그래서 Thread 클래스로 넘어가 멀티-스레드를 생성시키게 된다.
이 차이를 기억하면 스레드를 사용하기 위해선 run()이 아니라 start()로 스레드를 동작시키는 것이 필요함을 이해할 수 있을 것이다.
'⁂ Java > : 기본 익히기' 카테고리의 다른 글
[JAVA] #10-1 입출력스트림 : 입력(Input) - byte형과 char형 (0) | 2022.09.20 |
---|---|
[JAVA] #9-6 File 클래스 (0) | 2022.09.19 |
[JAVA] #9-4 제네릭(Generic) 이해하기 2 - 제네릭 (0) | 2022.09.16 |
[JAVA] #9-3 제네릭(Generic) 이해하기 1 - 컬렉션 프레임워크 (0) | 2022.09.16 |
[JAVA] #9-2 예외처리(Exception) 2 - throws (0) | 2022.09.16 |