❗️ 순차적 실행이 병렬실행 보다 빠른 경우도 있다. 동시 실행에 따르는 오버헤드가 없고, 단일 CPU 알고리즘은 하드웨어 작업에 더 친화적일 수 있기 때문이다.
✔️ 단순히 실행중인 프로그램
즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 의미.
이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성됨.
각각의 프로세스들은 자신만의 메모리 영역을 확보한 상태로 실행되고 있다는 것을 알 수 있다.
✔️ 프로세스 간의 통신하는 방법
프로세스 A에서 프로세스 B를 직접 접근할 수 없기 때문에 프로세스 간의 통신을 하는 특별한 방식이 필요하다. ➡️ 메일슬록(mailslot), 파이프(pipe)
프로세스는 독립적인 메모리 공간을 지니기 때문에 IP를 통하지 않고 통신할 수 없다.
프로세스가 여럿이 병렬적으로 실행되기 위해서는 필연적으로 컨텍스트 스위칭이 발생할 수 밖에 없다. 그것을 해결할 수 있는 것이 Thread이다.
✔️ 프로세스 내에서 실제로 작업을 수행하는 주체를 의미
❗️ 특정시간에 Thread1 특정시간에 Thread2 이렇게 시간을 쪼개서 각각의 스레드가 실행됐다 안됐다를 반복하면서 실행하게 된다.
스레드는 하나의 프로그램 내에 존재하는 여러 개의 실행 흐름을 위한 모델이다.
우리가 생각하는 프로그램이 실행되기 위해서 하나의 실행흐름으로 처리할 수도 있지만 다수의 실행흐름으로 처리할 수도 있다.
프로세스와 별개가 아닌 프로세스를 구성하고 실행하는 흐름이다.
모든 프로세스에는 한 개 이상의 스레드가 작업을 수행한다.
두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 한다.
프로세스와 프로세스의 전환보다, 스레드와 스레드가 전환하는 비용이 더 적다.
NEW
: 스레드가 생성되고 아직 start()
가 호출되지 않은 상태
RUNNABLE
: 실행 중 또는 실행 가능한 상태
BLOCKED
: 동기화 블럭에 의해서 일시정지된 상태(Lock이 풀릴 때까지 기다리는 상태)
WAITING, TIMED_WAITING
: 스레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrnnable) 일시정지 상태.
: TIMED_WAITING은 일시정지시간이 지정된 경우를 의미.
TERMINATED
: 스레드의 작업이 종료된 상태
스레드를 생성하고 start()
를 호출 ➡️ 실행대기열에 저장되어 자신의 차례가 될 때까지 기다림
❗️ 실행대기열은 큐(Queue)와 같은 구조
실행대기상태에 있다가 자신의 차례가 되면 실행상태가 됨 (실행대기열에 먼저 들어온 스레드가 실행)
주어진 실행시간이 다 되거나 yield()
를 만나면 다시 실행대기상태가 되고 다음 차례의 스레드가 실행상태가 됨
실행 중에 suspend()
, sleep()
, wait()
, join()
, I/O block
에 의해 일시정지상태가 될 수 있음.
사용자의 입력을 기다리는 경우를 예
: 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 됨
❗️ I/O block: 입출력작업에서 발생하는 지연상태
지정된 일시정지시간이 다되거나(time-out
), notify()
, resume()
, interrupt()
가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 됨.
실행을 모두 마치거나 stop()
이 호출되면 스레드는 소멸된다.
❗️ 꼭 번호의 순서대로 스레드가 수행되는 것은 아님
스레드는 프로세스 안에 존재하는 실행흐름이다.
스레드는 프로세스의 heap, data(static), code 영역등을 공유한다.
스레드는 stack 영역을 제외한 메모리 영역을 공유한다.
스레드가 code영역을 공유하기 때문에 프로세스 내부의 스레드들은 프로세스가 가지고 있는 함수를 자연스럽게 모두 호출할 수 있다.
스레드는 IPC없이도 스레드 간의 통신이 가능하다.
A, B 스레드는 통신하기 위해 heap영역에 메모리 공간을 할당하고, 두 스레드가 자유롭게 접근할 수 있다.
스레드는 프로세스처럼 스케쥴링의 대상이다. 이 과정에서 컨텍스트 스위칭이 발생한다. 하지만 스레드는 공유하고 있는 메모리 영역 덕분에 컨텍스트 스위칭 때문에 발생하는 오버헤드가 프로세스에 비해 작다.
❗️ 스케쥴링
: 멀티프로세스 운영체제에서 프로세스의 CPU 할당 순서 및 방법을 결정짓는 것
❗️ 컨텍스트 스위칭
: 동작중인 프로세스가 바뀔 때 프로세스는 현재 자신의 상태(context 정보)를 일단 보존한 후, 새롭게 동작 개시하는 프로세스는 이전에 보존해 두었던 자신의 컨텍스트 정보를 다시 복구하는 이런 현상을 의미한다.
➡️ 스레드의 컨텍스트 정보는 프로세스보다 적기 때문에 스레드의 컨텍스트 스위칭은 가볍게 행해지는 것이 보통이다. 하지만, 실제로 스레드와 프로세스의 관계는 JVM구현에 크게 의존한다.
✔️ Thread 클래스를 상속받는 방법
✔️ Runnable 인터페이스를 구현하는 방법
두 방법 모두 스레드를 통해 작업하고 싶은 내용을 run()
메서드에 작성하면 된다.
✔️ start()
: 스레드 실행 할 준비를 해주고,run()
를 실행 해 준다.
: 흐름이 하나 더 생긴다.
class Xxx extends Thread {
pubilc void run() {
// 동시에 실행될 코드 작성
}
}
Xxx x = new Xxx();
x.start();
public class MyThreadExam {
public static void main(String[] args) {
String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
System.out.println("thread name: " + name);
System.out.println("start!");
// 1초마다 *를 10번 출력하는 프로그램을 작성하시오.
for(int i = 0; i < 10; i++) {
System.out.print("*");
try {
Thread.sleep(1000); // 1초마다 쉰다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 1초마다 +를 10번 출력하는 프로그램을 작성하시오.
for(int i = 0; i < 10; i++) {
System.out.print("+");
try {
Thread.sleep(1000); // 1초마다 쉰다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end!");
}
}
thread name: main
start!
**********++++++++++end!
동시에 진행하지 않고 *가 끝나야 +가 나온다.
✔️ MyThread
// 1. Thread 클래스를 상속받는다.
public class MyThread extends Thread {
private String str;
public MyThread(String str) {
this.str = str;
}
// 2. run() 메서드를 오버라이딩 한다.
// 동시에 실행시키고 싶은 코드를 작성한다.
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.print(str);
try {
Thread.sleep(1000); // 1초마다 쉰다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
✔️ MyThreadExam
public class MyThreadExam {
public static void main(String[] args) {
String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
System.out.println("thread name: " + name);
System.out.println("start!");
MyThread mt1 = new MyThread("*");
MyThread mt2 = new MyThread("+");
// 3. Thread는 Start() 메서드를 실행한다.
mt1.start(); // 새로운 흐름이 생긴다.
mt2.start();
System.out.println("end!");
}
}
thread name: main
start!
*+end!
*+*+*+*+*+*+*++*+*
❗️ 스레드를 배우기 전에는
main()
메서드가 끝나면 종료되었지만 스레드를 배운 이후에는 모든 스레드가 종료되었을 때 프로그램이 종료된다는 것을 알아둬야 한다.
Runnable
은 스레드가 아닌 인터페이스이기 때문에 Start()
메서드가 없다.
그래서 Thread가 Runnable을 가지도록 만들어야 한다.
class xxx implements Runnable {
public void run() {
// 동시에 실행 될 코드 작성
}
}
Xxx x = new Xxx();
Thread t = new Thread(x);
t.start;
// 1. Runnable 인터페이스를 구현한다.
public class MyRunnable implements Runnable {
private String str;
public MyRunnable(String str) {
this.str = str;
}
// 2. run() 메서드를 오버라이딩 한다.
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("---" + name);
for(int i = 0; i < 10; i++) {
System.out.print(str);
try {
Thread.sleep(1000); // 1초마다 쉰다.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MyRunnableExam {
public static void main(String[] args) {
String name = Thread.currentThread().getName(); // 현재 스레드가 갖고있는 이름값
System.out.println("thread name: " + name);
System.out.println("start!");
MyRunnable mr1 = new MyRunnable("*");
MyRunnable mr2 = new MyRunnable("+");
// 3. Thread 인스턴스를 생성하는데, 생성자에 Runnable 인스턴스를 넣어준다.
Thread th1 = new Thread(mr1);
Thread th2 = new Thread(mr2);
// 4. Thread가 가지고 있는 start() 메서드를 호출한다.
th1.start();
th2.start();
System.out.println("end!");
}
}
❗️ 실행하다보면 스레드는 서로가 자원을 획득해서 서로가 빨리 실행하고 싶어한다. 그러다보니 실행되는 결과가 항상 똑같지가 않다.
References
: https://mer1.tistory.com/33
: http://www.tcpschool.com/java/java_thread_concept
: https://www.youtube.com/channel/UChWUWqURDfGFHpCIeLO8jZA
: https://velog.io/@matcha_/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90