1. 프로세스(process)와 스레드(thread)
1. 프로세스 (process)
실행 중인 프로그램으로써 자원과 스레드로 구성
2. 스레드 (Thread)
프로세스 내에서 실제 작업을 수행 , 프로세스는 최소한 하나의 스레드를 가진다.
결국 모든 프로세스는 최소한 하나 이상의 스레드를 가지고 있으며 , 실제로 프로세스의 자원을 가지고 작업을 수행하는 것은 스레드이다.
멀티태스킹과 멀티스레딩
여러 개의 프로세스의 실행 → 멀티태스킹
하나의 프로세스 안에서 여러 개의 스레드의 실행 → 멀티스레딩
하지만 하나의 CPU코어가 한 번에 수행할 수 있는 작업은 하나뿐이고 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 동시에 수행되어 보이는 것뿐, 따라서 프로세스가 얼마나 많은 스레드를 가지느냐는 성능의 좋음을 나타내는 건 아니다.
멀티스레딩의 장단점
- CPU의 사용률을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
메신저로 채팅하면서 파일을 다운로드하거나 음성 대화를 나눌 수 있는 것이 가능한 이유는 바로 멀티스레들 작성되어 있기 때문이다. 싱글 스레드(프로세스)라면 파일을 받는 동안 다른 작업은 전혀 할 수 없게 된다. 따라서 많은 사용자의 요청을 수행하려면 새로운 프로세스를 생성해야 하는데 스레드를 생성하는 것보다 더 많은 시간과 메모리를 사용하기 때문에 사용자가 늘어날수록 효율적이지 못하다.
하지만 멀티스레딩이 이런 장점만 갖고 있는 것은 아니다.
하나의 프로세스에서 자원을 공유하기 때문에 발생할 수 있는 동기화, 교착상태와 같은 문제들을 고려해야만 한다. 그리고 멀티스레딩은 싱글 스레딩보다 시간이 더 걸린다.
2. 스레드의 구현과 실행
구현
1.Thread 클래스를 상속
2.Runnable 인터페이스를 구현
왜 상속과 구현 두 가지가 존재할까? 해서 코드를 살펴봤더니
Thread는 Runnable의 구현체였던 것 따라서 Runnable을 직접 구현해 쓸 것이냐 구현한 Thread를 쓸것이냐 그 차이인 것이다.
하지만 우리는 객체지향에서 배웠듯 , 인터페이스를 구현하는 방법으로 코드의 재사용성을 높이고 일관성을 유지할 수 있기 때문에 아마 후자가 더 많이 사용되지 않을까 싶다.
Thread와 Runnable의 공통점은 바로 추상 메서드 Run()을 구현하는 것이다. 사실 스레드를 구현한다는 것이 바로 이 Run(){ /몸통/ }에서 몸통을 채워주는 것이기 때문,
예제를 통해서 Thread 구현 방법의 공통점과 차이를 확인해보자
public class ThreadEx1 {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();//Runnable 로 구현한 클래스의 인스턴스
Thread t2 = new Thread(r); // Thread클래스 생성자의 매개변수로 제공
// Ex1_1과 Ex1_2 두개의 스레드
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread {
@Override
public void run() { //run의 몸통 구현
for (int i = 0; i < 5; i++) {
System.out.println(getName());
}
}
}
class ThreadEx1_2 implements Runnable {
@Override
public void run() { //run의 몸통 구현
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
<!--실행결과-->
1
1
1
1
1
0
0
0
0
0
//스케줄러에 의한 실행결과이다.
Runnable 인터페이스를 구현한 경우의 Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread클래스의 생성자의 매개변수로 제공해야 한다.
왜 그런 걸까? 아래의 Thread 클래스의 소스코드를 보면 이해가 쉽게 될 것이다.
/* What will be run. */
private Runnable target; //Runnable을 구현한 target , 위의 코드에선 r
@Override
public void run() {
if (target != null) {
target.run(); //target(r)의 run을 실행
}
}
실행
Start() 가 호출된다고 해서 바로 실행되는 것은 아니다. 아까도 얘기했듯 동시에 실행하는것처럼 보일뿐 각자의 순서가 있고 그 순서대로 실행이 되는것 (by OS scheduler)
그리고 start는 하나의 스레드에 대해서 한 번만 호출 가능하다. ( 왜 그런지는 start와 run에서 설명 )
따라서 재실행하고 싶을 땐 새로운 스레드를 생성한 다음 다시 start를 호출해야 함
3. start 와 run
start 를 이용하여 스레드를 실행시키는 이유는 무엇일까? 우리는 run(){ / 몸통 / } 에 작업내용을 넣어서 실행시키는 게 아닌가?
run 을 호출하는 것은 단순히 Thread 클래스에 선언된 메서드를 호출하는 것일 뿐 , 스레드를 생성하는 건 아니다.
start 는 새로운 스레드가 작업을 실행하는데 필요한 호출 스택(call stack)을 생성한 다음에 run 을 호출해서, 생성된 호출 스택에 run 이 첫 번째로 올라가게 한다.
모든 스레드는 독립적인 작업을 수행하기 위해서 자신만의 호출 스택을 필요로 한다. 그리고 스레드가 종료되면 호출 스택은 소멸된다.
Main thread
메인 스레드는 쉽게 말해서 start를 호출하기 위해서 존재하는 스레드이다.
start 로 새로운 스레드를 생성하여 작업을 수행하려면 start를 호출하기 위한 최소한 하나의 스레드도 존재해야 하는데 그게 바로 main thread이다.
main thread가 실행되고 start 를 호출하여 새로운 스레드를 생성 후(생성 후 run 호출) main thread의 작업이 start 하나만이라면 main thread는 종료된다. 하지만 프로그램이 종료되는 순간은 start 로 수행한 작업이 완료되어 , 해당 스레드도 종료될 때 그때 프로그램은 종료된다.
아래 예제 코드로 정리해보자
예제 코드 1 - start()
public class ThreadEx2 {
public static void main(String[] args) {
ThreadEx2_1 t = new ThreadEx2_1();
t.start();
}
}
class ThreadEx2_1 extends Thread {
@Override
public void run() {
throwException();
}
public void throwException() {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
}
실행결과
java.lang.Exception
at Chapter13.ThreadEx2_1.throwException(ThreadEx2.java:18)
at Chapter13.ThreadEx2_1.run(ThreadEx2.java:13)
//호출스택의 첫 번째 메서드가 run이라는 뜻
main 스레드는 start로 새로운 호출 스택을 만든 후 종료되고 없어졌다.
(위의 코드는 start로 호출스택을 만든후 run메서드로 일부러 오류를 낸 코드이다.)
예제 코드 2 - run()
public class ThreadEx3 {
public static void main(String[] args) {
ThreadEx2_1 t = new ThreadEx2_1();
t.run();
}
}
class ThreadEx3_1 extends Thread {
@Override
public void run() {
throwException();
}
public void throwException() {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
}
실행결과
java.lang.Exception
at Chapter13.ThreadEx2_1.throwException(ThreadEx2.java:18)
at Chapter13.ThreadEx2_1.run(ThreadEx2.java:13)
at Chapter13.ThreadEx3.main(ThreadEx3.java:6)
//main thread가 종료되지 않았다.
run()을 호출하는 것은 단순히 메서드의 호출이다. 따라서 main thread에서 run이 실행되고 main메서드에서 오류가 발생한다.
'Java > Java Study' 카테고리의 다른 글
[ 객체지향 ] static (0) | 2021.07.08 |
---|---|
[Collections Framework] Arrays.sort( ); - (Comparable & Comparator) (0) | 2021.06.03 |
[Collections Framework] Arrays (0) | 2021.06.03 |
[객체지향] Static 메서드는 언제 쓸까? (0) | 2021.06.02 |
[Collections Framework] LinkedList (vs Array) (0) | 2021.05.31 |