프로세스와 쓰레드
- 프로세스는 '실행 중인 프로그램(program)' 입니다.
- 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 됩니다.
- 프로세스는 '프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 + 쓰레드' 로 구성되어 있습니다.
- 프로세스의 자원을 이용해 실제로 작업을 수행하는 것이 바로 Thread입니다.
- 모든 프로세스는 하나 이상의 쓰레드를 가지며, 둘 이상의 쓰레드를 가진 프로세스를 '멀티쓰레드 프로세스( multi-threaded process)'라고 합니다.
멀티쓰래딩의 장단점
멀티쓰레딩의 장점
1. CPU의 사용률을 향상시킨다.
2. 자원을 보다 효율적으로 사용할 수 있다.
3. 사용자에 대한 응답성이 향상된다.
4. 작업이 분리되어 코드가 간결해진다.
멀티쓰레딩의 단점
1. 동기화( Synchronization ), 교착상태( Deadlock )와 같은 문제들이 발생할 수 있다.
쓰레드의 구현과 실행
쓰레드의 구현
- 쓰레드를 구현하는 방법은 다음과 같이 두 가지가 있습니다.
- Thread 클래스를 상속 받는 방법
- Runnable 인터페이스를 구현하는 방법
- 쓰레드를 구현할 때 어느 쪽을 선택해도 별 차이가 없지만 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 것이 일반적인 방법입니다.
public class ThreadEx1 {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName());
}
}
}
class ThreadEx1_2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
- 위 코드에서 확인할 수 있듯이 Thread 클래스를 상속받은 경우와 Runnable 인터페이스를 구현한 경우의 인스턴스 생성 방법이 다릅니다.
- Runnable 인터페이스를 구현한 경우, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성하여 Thread 클래스의 생성자의 매개변수로 전달해야 합니다.
- Thread 클래스를 상속받는 경우, 자손 클래스에서 조상인 Thread 클래스의 메서드를 직접 호출할 수 있지만, Runnable을 구현하면 Thread 클래스의 static 메서드인 currentThread() 를 호출하여 쓰레드에 대한 참조를 얻어 와야 합니다.
- 쓰레드의 이름은 다음과 같은 생성자나 메서드를 통해 지정 또는 변경할 수 있으며, 쓰레드의 이름을 지정하지 않으면 ' Thread - 번호 '의 형식으로 이름이 정해집니다.
Thread(Runnable target, String name)
Thread(String name)
void setName(String name)
쓰레드의 실행 - start( )
- 쓰레드를 실행하는 메서드입니다.
- start() 메서드를 호출한다고 쓰레드가 바로 실행되는 것이 아니며, OS 스케쥴러에 의해 자기 차례가 된 경우 쓰레드가 실행됩니다.
- 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있습니다. 하나의 쓰레드에 대해 start()를 두 번 이상 호출하는 경우 IllegalThreadStateException이 발생합니다.
start() 와 run()
run()
- main 메서드에서 run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아닌 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐입니다.
start()
- 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택( call stack )을 생성한 다음 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 합니다.
- 모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에, 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸됩니다.
- 새로운 쓰레드를 생성하고 start()를 호출한 후 호출스택의 변화
(1) main 메서드에서 쓰레드의 start()를 호출합니다.
(2) start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성합니다.
(3) 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행합니다.
(4) 이제 호출 스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행됩니다.
main 쓰레드
- main 메서드의 작업을 수행하는 쓰레드입니다.
- 프로그램을 실행하면 기본적으로 하나의 쓰레드를 생성하고, 그 쓰레드가 main 메서드를 호출해서 작업이 수행되도록 합니다.
싱글쓰레드와 멀티쓰레드
- 두 개의 작업을 하나의 쓰레드로 처리하는 경우와 두 개의 쓰레드로 처리하는 경우를 가정해보겠습니다.
- 하나의 쓰레드로 두 작업을 처리하는 경우는 한 작업을 마친 후에 다른 작업을 시작하지만, 두 개의 쓰레드로 작업 하는 경우에는 짧은 시간동안 두 개의 쓰레드가 번갈아 가면서 작업을 수행합니다.
- 두 개의 쓰레드로 작업을 하는 경우 쓰레드 간 작업 전환( context switching )에 시간이 걸리기 때문에, 싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 멀티 쓰레드보다 싱글 쓰레드로 프로그래밍 하는 것이 더 효율적일 수 있습니다.
- 두 개의 쓰레드가 서로 다른 자원을 사용하는 작업의 경우에는 싱글쓰레드 프로세스보다 멀티쓰레드 프로세스가 더 효율적입니다. ( 예를 들면 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일을 주고받는 작업, 프린터로 파일을 출력하는 작업과 같이 외부기기와의 입출력을 필요로 하는 경우가 이에 해당합니다. )
하나의 쓰레드로 사용자 입력과 출력 처리하는 코드
class ThreadExA {
public static void main(String[] args) throws Exception {
String input = JOptionPane.showInputDialog("사용자 값 입력.");
System.out.println("입력하신 값은 " + input + "입니다. ");
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000); // 1초마다 숫자를 출력하기 위함
} catch (Exception e) {
}
}
}
}
출력 결과 - 사용자의 입력을 마치기 전까지는 화면에 숫자가 출력되지 않습니다.
두 개의 쓰레드로 사용자 입력과 출력 처리하는 코드
class ThreadB{
public static void main(String[] args) {
CustomThread thread = new CustomThread();
thread.start();
String input = JOptionPane.showInputDialog("사용자 값 입력.");
System.out.println("입력하신 값은 " + input + "입니다.");
}
// 내부 클래스
private static class CustomThread extends Thread{
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
sleep(1000);
} catch (Exception e) {
}
}
}
}
}
출력 결과 - 사용자의 입력과 숫자 출력을 두 개의 쓰레드로 나누어 처리했기 때문에, 사용자가 입력을 마치지 않아도 화면에 숫자가 출력됩니다.
데몬 쓰레드 ( daemon thread )
- 데몬 쓰레드는 다른 일반 쓰레드( 데몬 쓰레드가 아닌 쓰레드 )의 작업을 돕는 보조적인 역할을 하는 쓰레드입니다.
- 일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료됩니다.
- 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 데몬 쓰레드에 해당합니다.
- 데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성합니다.
- 데몬 쓰레드는 일반 쓰레드와 작성방법과 실행방법이 같으며 쓰레드를 생성한 후 실행하기 전에 setDaemon(true)를 호출하여야 합니다.
- 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 됩니다.
boolean isDaemon() : 쓰레드가 데몬 쓰레드인지 확인하는 메서드. 데몬 쓰레드이면 true를 반환합니다.
void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경하는 메서드입니다.
데몬 쓰레드 설정 코드
class ThreadExDaemon implements Runnable{
static boolean autoSave = false;
public static void main(String[] args) {
Thread thread = new Thread(new ThreadExDaemon());
thread.setDaemon(true);
thread.start();
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(i);
if (i == 5) {
autoSave = true;
}
}
System.out.println("프로그램을 종료합니다.");
}
@Override
public void run() {
// 무한 루프와 조건문을 사용해서 구현
while (true) {
try {
// 3초마다 확인
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {}
if (autoSave) {
autoSave();
}
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
- 3초마다 autoSave의 값을 확인해서 그 값이 true이면, autoSave()를 호출하는 일을 무한히 반복하는 데몬 쓰레드를 생성하였습니다.
- 이 쓰레드를 데몬 쓰레드로 설정하지 않았다면, 무한루프로 인해 프로그램 강제종료를 하지 않는 한 종료되지 않을 것입니다.
- setDaemon 메서드는 반드시 start()를 호출하기 전에 실행되어야 합니다.
( 그렇지 않으면IllegalThreadStateException이 발생합니다. )
'Java' 카테고리의 다른 글
[ Java의 정석 ] 쓰레드( Thread ) [ 2 ] (2) | 2023.09.09 |
---|---|
[ JAVA의 정석 ] 지네릭스 ( Generics ) (0) | 2023.08.13 |
[ Java 의 정석 ] 내부 클래스 ( Inner class ) (0) | 2023.08.03 |
[ Java의 정석 ] 인터페이스 ( interface ) (0) | 2023.08.01 |
[ Java의 정석 ] 추상클래스 ( abstract class ) (0) | 2023.07.29 |