여러 CPU: 요리사
여러 프로세스: 라면 조리대, 피자 조리대, 자장면 조리대
여러 스레드: 4개의 라면 버너, 5개의 피자 화덕, 3개의 자장면 가스
용어 | 멀티프로세싱 (=multi processing) | 멀티태스킹 (= multi tasking) | 멀티스레딩 (= multi threading) |
---|---|---|---|
관점 | 시스템 관점 | 프로그램 외부에서의 관점 | 프로그램 내부에서의 관점 |
의미 | CPU 여러개에서 동시에 여러개의 프로세스 수행 | CPU 1개에서 동시에 여러 프로그램 실행 | Processor 1개가 동시에 여러 스레드 실행 |
예시 | 다수의 송금 거래를 동시에 처리하는 은행전산 시스템 | PC 카톡 켜놓고 YouTube 음악 들으면서 온라인 뱅킹 업무 | 프로그램 안에서 실행되는 코드의 흐름이 여러개 |
스레드를 생성하는 방법은 두 가지이다.
1. Thread 클래스를 상속 받아서 생성
2. Runnable 인터페이스 구현
방법이 두 가지인 이유는 자바가 단일 상속만 허용하기 때문이다. 만약 상속받아야 하는 클래스가 있다면 Thread 클래스를 상속 받는 방법 대신 Runnable 인터페이스를 구현하는 방법을 사용할 수 있다.
생성자 | 내용 |
---|---|
Thread() | 일반적인 스레드 객체 생성 Thread-n 이런 이름을 가진 스레드가 만들어진다. |
Thread(Runnable target) | run() 메서드를 가지는 객체를 인자값으로 할당하기 |
Thread(Runnable target, String name) | run() 메서드를 가지는 객체와 스레드 이름을 인자값으로 할당하기 |
Thread(String name) | 스레드 생성하면서 스레드 이름 지어주기 |
public class Test01 extends Thread{
@Override
public void run() {
/* 스레드 실행코드 */
}
}
public class Test01 implements Runnable {
@Override
public void run() {
/* 스레드 실행코드 */
}
}
스레드가 실행되기 위한 준비단계.
CPU를 점유하고 있지 않으며 실행(Running 상태)을 하기 위해 대기하고 있는 상태.
코딩 상에서 start() 메소드를 호출하면 run() 메소드에 설정된 스레드가 Runnable 상태로 진입한다.
"Ready" 상태라고도 한다.
CPU를 점유하여 실행하고 있는 상태.
run() 메서드는 JVM만이 호출 가능하다.
Runnable(준비 상태)에 있는 여러 스레드 중 우선 순위를 가진 스레드가 결정되면 JVM이 자동으로 run() 메서드를 호출하여 스레드가 Running 상태로 진입한다.
Running 상태에서 스레드가 모두 실행되고 난 후 완료 상태.
"Done" 상태라고도 한다.
CPU 점유권을 상실한 상태.
후에 특정 메서드를 실행시켜 Runnabl(준비상태)로 전환한다.
wait() 메서드에 의해 Blocked 상태가 된 스레드는 notify() 메소드가 호출되면 Runnable 상태로 돌아간다.
sleep(시간) 메서드에 의해 Blocked 상태가 된 스레드는 지정된 시간이 지나면 Runnable 상태로 돌아간다.
start() | run() |
---|---|
새로운 Thread가 생성되며 Thread가 시작되면 run() 메서드가 실행된다. | Thread가 생성되지 않으며 그냥 run() 메서드만 실행된다. |
동일한 객체에서 두 번 이상 호출시 IllegalThreadStateException 예외가 발생한다. | 호출 수에 제한 없이 계속 호출할 수 있다. |
멀티스레드로 동작한다. | 싱글스레드로 동작한다. |
runnable에는 start() 메서드가 없다.
thread의 start() 메서드가 runnable의 run() 메서드를 실행시켜주는 것이다.
Thread.start() 호출 → Thread 생성 → Thread가 Runnable의 run() 실행
public class ExamThread {
public static void main(String[] args) {
int count = 1000;
// 1단계
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
super.run();
}
}
MyThread1 thread = new MyThread1();
thread.start();
// 2단계
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
super.run();
}
}
new MyThread2().start();
// 3단계
new Thread() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
super.run();
}
}.start();
for (int i = 0; i < count; i++) {
System.out.println(">>> " + i);
}
}
}
public class ExamRunnable {
public static void main(String[] args) {
int count = 1000;
// 1단계
class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
}
}
MyRunnable1 r = new MyRunnable1();
Thread t1 = new Thread(r);
t1.start();
// 2단계
class MyRunnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
}
}
Thread t2= new Thread(new MyRunnable2());
t2.start();
// 3단계 - 스레드 변수는 재사용할 수 없으므로 변수로 만들어줄 필요 없다.
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
}
}
new Thread(new MyRunnable3()).start();
// 4단계 -Runnable 안의 코드의 길이가 짧은 편이므로 익명 클래스로 만들어준다.
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
}
}).start();
// 5단계 - 인터페이스 안에 함수가 한 개인 형태이므로 람다 문법을 사용한다.
new Thread(
() -> {
for (int i = 0; i < count; i++) {
System.out.println("==> " + i);
}
}
).start();
for (int i = 0; i < count; i++) {
System.out.println(">>> " + i);
}
}
}
스레드의 생명주기 잘 설명해 놓은 블로그: https://coding-factory.tistory.com/279