즉 프로세스는 CPU, 메모리 등의 자원과 스레드로 구성되어있고, 프로세스에 할당된 자원을 가지고 실제로 작업을 수행하는건 스레드이다. 그리고 프로세스에 자원을 할당해주는건 운영체제이다.
하나의 프로세스가 가질 수 있는 쓰레드의 개수는 제한되어 있지 않으나 스레드가 작업을 수행하는데 개별적인 메모리 공간(호출스택)을 필요로 하기 때문에 프로세스의 메모리 한계에 따라 생성할 수 있는 스레드의 수가 결정된다. 하지만 메모리 걱정은 안해도된다.
멀티태스킹(multi-tasking)과 멀티스레딩(multi-threading)
현재 우리가 사용하고 있는 윈도우나 유닉스를 포함한 대부분의 OS는 멀틴태스킹을 지원하기 때문에 여러 개의 프로세스가 동시에 실행될 수 있다.
이와 마찬가지로 멀티스레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다. CPU의 코어가 한 번에 단 하나의 작업만 수행할 수 있으므로, 실제로 동시에 처리되는 작업의 개수는 코어(core)의 개수와 일치한다. 그러나 처리해야하는 스레드의 수는 언제나 코어의 개수보다 훨씬 많기 때문에 각 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보이게 한다.
그래서 프로세스의 성능이 단순히 스레드의 개수에 비례하는 것은 아니며, 하나의 스레드를 가진 프로세스 보다 두 개의 스레드를 가진 프로세스가 오히려 더 낮은 성능을 볼 수도있다.
스레드의 구현과 실행
스레드를 구현하는 방법은 Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법 모두 두 가지가 있다. 어느 쪽을 선택해도 별 차이는 없지만 Thread클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable인터페이스를 구현하는 방법이 일반적이다.
그리고 Runnable인터페이스를 구현하는 방법은 재사용성(reusability)이 높고 코드의 일관성(consistency)을 유지할 수 있기 때문에 보다 객체지향적인 방법이라 할 수 있겠다.
자바에서는 작업 스레드도 객체로 생성되기 떄문에 클래스가 필요하다. java.lang.Thread 클래스를 직접 객체화해서 생성해도 되지만, Thread를 상속해서 하위 클래스를 만들어 생성할 수도 있다.
Thread 클래스로부터 직접 생성
java.lang.Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 다음과 같이 Runnable을 매개값으로 갖는 생성자를 호출해야 한다.
Thread thread = new Thread(Runnable target);
Runnable은 작업 스레드가 실행할 수 있는 코드를 가지고 있는 객체라고 해서 붙여진 이름이다. Runnable 에는 run() 메소드 하나가 정의되어 있는데, 구현 클래스는 run()을 재정의해서 작업 스레드가 실행할 코드를 작성해야 한다.
작업 스레드는 생성되는 즉시 실행되는 것이 아니라, start() 메소드를 호출해야 실행된다.
스레드 생성 및 실행하는 방법
1
2
3
|
public class T extends Thread{
//Thread 클래스를 상속받은 후에 run() 메소드를 재정의함
//run() 메소드 안에 작업을 기술함.
}
|
cs |
1
2
3
4
5
6
7
8
9
|
public class Test {
public static void main(String[] args) {
Thread t = new T();
t.start();
}
}
|
cs |
1
2
3
4
5
6
7
8
9
10
|
public class T2 implements Runnable{
//Runnable 인터페이스를 구현하고 run() 메소드를 작성함
//이 클래스의 객체를 Thread 클래스의 생성자를 호출할 때 전달함
@Override
public void run() {
}
}
|
cs |
1
2
3
4
5
6
7
8
9
|
public class Test2 {
public static void main(String[] args) {
Thread t = new Thread(new T());
t.start();
}
}
|
cs |
메소드 | 설 명 |
static void sleep(long mills) | Thread를 mills 시간 동안 재움 (mills : 밀리초) Thread가 sleep된 동안 인터럽트가 된다면 InterruptedExeption이 발생함; 이 예외를 처리해야 함 |
void run() | Thread 시작 시 JVM 의해 호출됨 이 메소드 종료 시 Thread도 종료됨 Thread가 수행할 작업을 이 메소드 안에 오버라이딩하여 작성 |
void start() | JVM에게 Thread 실행을 요청 |
void join() | Thread가 종료할 때까지 기다림 |
static void yield() | Thread가 다른 Thread에게 실행을 양보함 이때 Thread 스케쥴링이 실행되고 다른 Thread가 선택/실행됨 |
void interrupt() | Thread를 강제 중단 |
스레드 실습
public class BeepPrintExample1 {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<5; i++) {
toolkit.beep();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
cs |
위 예제는 비프음 발생시키고자 하는 예제이다 메인 스레드만 사용하므로 0.5초마다 비프음이 들리고 나서 0.5초마다 '띵'이 출력된다. 이제 스레드를 사용하여 두가지 작업이 동시에 같이 되도록 해보자
Runnable 인터페이스를 활용한 Thread 실습
public class BeepTask implements Runnable{
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 5; i++) {
toolkit.beep();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
cs |
public class BeepPrintExample2 {
public static void main(String[] args) {
Runnable beepTask = new BeepTask();
Thread thread = new Thread(beepTask);
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
cs |
BeepTask 클래스에 Runnable 인터페이스를 상속받아 run() 메소드에 0.5초마다 비프음을 내는 코드를 작성하였고 BeepPrintExample2에서 thread.start()를 통해 비프음을 내는 동시에 '띵'이 출력되었다. 즉 동시에 두작업이 실행되는 것이다.
이번에는 Thread 하위 클래스로부터 생성해보자
public class BeepThread extends Thread{
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for (int i = 0; i < 5; i++) {
toolkit.beep();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
cs |
public class BeepPrintExample3 {
public static void main(String[] args) {
Thread thread = new BeepThread();
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("띵");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
cs |
class MyRunnable implements Runnable {
String myName;
public MyRunnable(String name) {
myName = name;
}
@Override
public void run() {
for (int i = 10; i >= 0; i--)
System.out.print(myName + i + " ");
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable("A"));
Thread t2 = new Thread(new MyRunnable("B"));
t1.start();
t2.start();
}
}
|
cs |
최수경 교수님, 『네트워크 프로그래밍』, 삼육대학교(2018)