반응형
반응형
해당 글에서는 스레드에 대한 정의 구조, 상태, 예시와 단일 스레드, 멀티 스레드에 대한 이해를 돕기 위한 목적으로 작성한 글입니다.
1) 스레드(Thread)
1. 스레드(Thread)
💡 스레드(Thread)란?
- 하나의 프로세스 안에서 독립적으로 실행되는 ‘작은 실행 단위’를 의미합니다.
💡 프로세스(Process)란?
- 시스템에서 실행 중인 프로그램을 의미합니다.
2. Java에서 스레드(Thread)
💡 Java에서 스레드란?
- Java에서 스레드는 ‘멀티 스레드(Multi-Thread)’을 지원하여 하나의 프로세스 안에 한 개 이상의 스레드를 지원하는 구조입니다.
- Java에서는 멀티 스레드를 통해 ‘비동기식 및 병렬 애플리케이션’을 개발할 수 있습니다
[ 더 알아보기 ]
💡 비동기식 및 병렬 애플리케이션이란?
- 애플리케이션이 실행 중인 동안 사용자가 다른 작업을 수행할 수 있도록 합니다. 웹 브라우저에서는 웹 페이지를 로드하는 동안 사용자가 다른 작업을 수행할 수 있습니다. 예를 들어, 이메일을 보내는 애플리케이션은 이메일을 보내는 작업과 동시에 사용자의 이메일을 읽는 작업을 수행할 수 있습니다.
💡 블로킹 (blocking I/O)란?
- I/O가 동작되고 있는 동안에 다른 일을 처리하지 못하는 상태를 의미하며 함수가 모든 일을 마무리될 때까지 다음 처리가 안되는 것을 의미합니다.
💡 논 블로킹(Non-blocking I/O)란?
- I/O가 동작을 하면서 request를 받으면 바로 다음 처리에 요청을 보내 놓고 다른 작업을 처리하다가 먼저 요청한 작업이 끝나면 이벤트를 받아서 응답을 보내는 것을 의미합니다.
[참고] Node의 스레드에 대해서 관심이 있으시면 아래의 링크를 이용하시면 됩니다.
2) 스레드의 구조
💡 스레드는 시스템의 하나의 프로세스 공간에서 수행되기에 단일스레드와 멀티스레드로 구분한 구조를 확인합니다.
1. 요약
💡 쓰레드에서는 프로세스 내에서 함께 공유해서 사용하는 영역과 스레드 내의 공유를 하지 않는 영역으로 나뉩니다.
구분 | 설명 | 공유 여부 | 동기화 필요여부 |
Code 영역 | 프로그램 코드 | 공유 | 불필요 |
Data 영역 | 전역 변수 및 정적 변수 | 공유 | 불필요 |
Heap 영역 | 동적 할당된 메모리 | 공유 | 필요 |
Stack 영역 | 지역 변수 및 매개변수 | 비공유 | 불필요 |
2. Code 영역
💡 Code 영역이란?
- 프로그램의 ‘코드가 저장’되는 영역을 의미합니다.
- Thread들은 같은 코드 영역을 공유하며 이 영역은 읽기 전용입니다. 따라서 Thread 간에는 이 영역에 대한 동기화가 필요하지 않습니다.
3. Data 영역
💡 Data 영역이란?
- ‘전역 변수와 정적 변수가 저장’되는데 해당 영역은 ‘프로그램이 실행되고 종료될 때까지 메모리에 존재’하는 영역을 의미합니다.
- Thread들은 같은 Data 영역을 공유합니다. 이 영역도 읽기 전용이므로 Thread 간에는 Data 영역에 대한 동기화가 필요하지 않습니다. 하지만 여러 Thread가 동시에 쓰는 경우에는 동기화 문제가 발생할 수 있습니다.
[ 더 알아보기 ]
💡 전역변수(Global Variable)
- 전역변수는 클래스 내부에서 모든 메서드에서 사용 가능한 변수입니다. 클래스 내부 어디서든 참조 가능하며 클래스가 생성될 때 자동으로 초기화됩니다.
💡 지역변수(Local Variable)
- 지역변수는 메서드나 블록 내에서 선언되는 변수로, 선언된 블록 안에서만 참조 가능합니다. 메서드가 호출될 때 생성되며, 메소드가 종료될 때 소멸됩니다.
4. Heap 영역
💡 Heap 영역이란?
- 동적 할당된 ‘메모리가 저장’되는 영역을 의미합니다.
- Thread 간에 Heap 영역을 공유합니다. 이 영역은 여러 Thread가 동시에 쓰는 경우 동기화 문제가 발생할 수 있으므로 적절한 동기화 기법을 사용해야 합니다.
5. Stack 영역
💡 Stack 영역이란?
- 함수 호출 시 생성되는 ‘지역 변수와 매개변수’가 저장되며 함수가 호출되었을 때 임시로 저장되고 ‘함수의 호출이 끝나면 해당 영역의 메모리에서 해제’가 되는 영역을 의미합니다.
- 이 영역은 Thread 마다 독립적으로 존재하며 Thread 간에 공유되지 않습니다. 따라서, 스택 영역에서는 동기화 문제가 발생하지 않습니다.
[ 더 알아보기 ]
💡 Java에서 스레드 동기화 문제란?
- 스레드는 프로그램이 동시에 여러 작업을 수행할 수 있도록 해주는 기능입니다. 하지만 ‘여러 스레드가 동시에 하나의 자원에 접근하면서 발생하는 문제’가 동기화 문제입니다.
- 스레드는 실행되는 시간이 OS에 의해 관리되기 때문에 어떤 스레드가 먼저 실행될지 예측할 수 없습니다. 따라서 여러 스레드가 동시에 하나의 자원에 접근하면, 결과가 예측할 수 없게 됩니다. 또한, 스레드가 자원에 접근하는 순서에 따라서는 원하는 결과를 얻을 수 없는 경우도 있습니다.
3) 단일 스레드(Single Thread), 멀티 스레드(Multi Thread)
💡 단일 스레드(Single Thread) 란?
- 하나의 프로세스 내에서 하나의 스레드만 실행되는 것을 말합니다(순차 실행). 프로그램이 하나의 작업만 처리할 수 있다는 의미이며, 다른 작업이 실행되기 전에 현재 작업이 완료되어야 합니다.
- 멀티 태스킹을 지원하지 않고 하나의 태스크만 처리할 수 있으므로 처리량이 낮아지는 단점이 있습니다.
💡 멀티 스레드(Multi Thread) 란?
- 하나의 프로세스 내에서 동시에 여러 개의 스레드가 실행되는 것을 말합니다(병행 실행). 스레드가 동시에 여러 작업을 처리할 수 있기 때문에 시스템의 성능을 향상할 수 있습니다.
- 프로그램의 작업을 분할하여 처리하기 때문에 다양한 작업을 동시에 처리하여 빠르고 효율적으로 실행할 수 있습니다. 그러나 스레드 간의 동기화 같은 부작용으로 이를 고려하여 프로그래밍해야 합니다.
[ 더 알아보기 ]
💡 순차실행(Sequential Execution)이란?
- 스레드의 순차실행은 각 스레드가 순서대로 실행되는 것을 의미합니다.
- 하나의 스레드가 실행 중이면, 다른 스레드는 대기 상태에 있습니다. 순차실행은 여러 스레드가 동시에 처리해야 하는 작업을 처리하기에는 적합하지 않습니다.
💡 병행실행(Concurrency Execution)
- 스레드의 병행 실행은 여러 개의 스레드가 동시에 실행되는 것을 의미합니다. 각 스레드는 서로 독립적으로 실행되기 때문에, 여러 개의 스레드가 협력하여 하나의 작업을 처리할 수 있습니다. 병행 실행은 여러 작업을 동시에 처리해야 하는 경우에 적합합니다.
💡 단일 스레드보다 멀티스레드가 당연히 좋은 것 아닌가?
- 멀티스레드는 한 번에 여러 작업을 처리할 수 있지만, 스레드 간의 경쟁으로 인해 자원 공유 및 동기화 문제가 발생할 수 있습니다. 이는 올바르게 처리하지 않으면 예측할 수 없는 결과를 초래할 수 있습니다.
- 반면, 단일 스레드는 하나의 작업을 처리하고 다음 작업으로 이동합니다. 이러한 접근 방식은 자원 공유와 동기화 문제를 방지할 수 있습니다. 또한 단일 스레드의 실행 순서는 예측 가능합니다. 따라서 멀티스레드와 단일 스레드는 각각의 장단점이 있으며, 상황에 따라 선택해야 합니다.
구분 | 단일 스레드(Single Thread) | 멀티 스레드(Multi Thread) |
처리 속도 | 느림 | 빠름 |
성능 | 낮음 | 높음 |
CPU 활용 | 낮음 | 높음 |
병목현상 | 발생 | 완화 |
안정성 | 높음 | 낮음 |
디버깅 | 쉬움 | 어려움 |
코딩 | 간단 | 복잡 |
확장성 | 낮음 | 높음 |
효율성 | 낮음 | 높음 |
예측성 | 높음 | 낮음 |
비용 | 낮음 | 높음 |
4) 스레드 상태 (Java thread status)
💡 스레드의 상태는 JVM에 의해서 생성 및 관리가 됩니다.
1. 스레드 상태 이해하기
열거 상수 | 상태 | 설명 |
Thread.State.NEW | 객체 상태 | 스레드가 생성되었지만 아직 시작되지 않은 상태 |
Thread.State.RUNNABLE | 실행 대기 | 스레드가 실행 가능한 상태 |
Thread.State.BLOCKED | 일시 정지 | 스레드가 일시적으로 중단된 상태 |
Thread.State.WAITING | 일시 정지 | 스레드가 다른 스레드의 특정 작업 완료를 기다리는 상태 |
Thread.State.TIMED_WAITING | 일시 정지 | 스레드가 일정 시간 동안 기다리는 상태 |
Thread.State.TERMINATED | 종료 | 스레드가 실행을 완료한 상태 |
[ 더 알아보기 ]
💡 열거형(enum) 상수란?
- 서로 연관된 상수들의 집합을 의미합니다. Java에서는 열거형을 선언하여 새로운 열거형 타입을 만들 수 있습니다. 열거형을 사용하면 코드를 더 읽기 쉽고 유지보수하기 쉽게 만들 수 있습니다.
💡 [ 스레드 상태 과정 설명]
1. Thread는 NEW라는 스레드가 생성된 상태로 시작됩니다.
2. Thread는 RUNNABLE이라는 실행이 가능한 상태로 시작이 됩니다.
3. Thread는 실행이 됩니다.
4. Thread는 각각의 상태가 될 수 있습니다.
- BLOCKED : 일시적으로 중단된 상태
- WATING : 다른 스레드가 작업 완료 될 때를 기다리는 상태
- TIMED_WATING : 일정시간 동안 기다리는 상태
- TERMINATED : 실행을 완료한 상태
5. WATING 상태일 때라면, 다른 스레드의 작업을 완료를 기다리는 상태로 Thread B가 수행될 때를 기다립니다.
6. 각각이 상태에서 다시 RUNNABLE 상태로 대기 상태가 됩니다.
2. 스레드 메서드
메서드 | 설명 |
start() | 스레드를 시작한다. |
run() | 스레드의 작업을 정의한다. |
sleep(long millis) | 현재 스레드를 주어진 시간(밀리초)만큼 일시 중지한다. |
yield() | 현재 스레드를 일시 중지하고 다른 스레드에게 실행 기회를 양보한다. |
join() | 다른 스레드가 종료될 때까지 현재 스레드를 일시 중지한다. |
isAlive() | 스레드가 실행 중인지 여부를 반환한다. |
setName(String name) | 스레드의 이름을 지정한다. |
getName() | 스레드의 이름을 반환한다. |
setPriority(int priority) | 스레드의 우선순위를 지정한다. |
getPriority() | 스레드의 우선순위를 반환한다. |
interrupt() | 스레드의 작업을 중단시킨다. |
3. 스레드 관련 Object 메서드
💡 wait(), notify(), notifyAll() 메서드는 모두 Object 클래스의 메서드이기 때문에, 어떤 객체에서든 호출할 수 있습니다.
메서드 | 설명 |
wait() | 현재 스레드를 일시적으로 중지시키고, 다른 스레드가 notify() 또는 notifyAll() 메서드를 호출할 때까지 기다리게 합니다. |
notify() | wait() 메서드에 의해 일시적으로 중지된 스레드 중 하나를 실행 대기 상태로 변경합니다. 여러 개의 스레드가 wait() 중인 경우, 이 중 하나만을 임의로 선택해 실행 대기 상태로 변경합니다. |
notifyAll() | wait() 메서드에 의해 일시적으로 중지된 모든 스레드를 실행 대기 상태로 변경합니다. |
5) 스레드 상태 별 실행 테스트
1. 스레드 실행
💡 해당 예시에서는 main() 함수에 스레드를 생성하면 run() 함수에서 이를 수행하고 종료하는 과정을 테스트합니다.
💡 Thread.start()
- 스레드를 시작합니다. 시작을 하게 되면 run()이 수행이 됩니다.
💡 Thread.run()
- 스레드 작업을 정의합니다.
public class ThreadSample extends Thread {
@Override
public void run() {
System.out.println("스레드를 시작합니다.");
}
/**
* Thread의 메인
*
* @param args
*/
public static void main(String[] args) {
ThreadSample sample = new ThreadSample();
// 스레드를 시작합니다.
sample.start();
}
}
2. 멀티스레드 실행
💡 해당 예시에서는 이전에 1개의 스레드만 생성하는 것이 아닌 10개의 스레드를 생성하고 종료하는 것을 확인해 보기 위한 예시입니다.
[예시 조건]
- main() 함수에서 반복문으로 스레드를 10개 생성을 하고 run() 함수를 10번 수행하면서 반복문의 값(1~10)을 전달하였습니다.
- run() 함수에서는 전달받은 값을 기반으로 몇 번째 스레드인지 콘솔을 남겼고 또한 Thread.sleep()을 통해서 1초간의 딜레이를 주었습니다.
💡 Thread.sleep(long millis)
- 현재 스레드를 주어진 시간(밀리초)만큼 일시 중지한다.
public class ThreadSample extends Thread {
int seq;
/**
* 시퀀스를 정의 하는 생성자 구성
*
* @param seq
*/
public ThreadSample(int seq) {
this.seq = seq;
}
@Override
public void run() {
System.out.println(seq + "번째 스레드를 시작합니다.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(seq + "번째 스레드를 종료합니다.");
}
/**
* Thread의 메인
*
* @param args
*/
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
ThreadSample sample = new ThreadSample(i);
// 스레드를 시작합니다.
sample.start();
}
System.out.println("메인 메서드가 종료되었습니다.");
}
}
[결과 확인]
1. 스레드는 반복문의 순차적으로 시작되지 않았습니다.
2. 스레드의 종료는 시작한 순서대로 종료하지 않았습니다.
3. 메인 메서드가 제일 먼저 종료가 되었습니다.
💡 해당 결과로 멀티 스레드는 순차적으로 시작되지 않고 순차적으로 종료되지 않는 점을 알게 되었습니다.
오늘도 감사합니다. 😀
반응형
'Java > 아키텍처 & 디자인 패턴' 카테고리의 다른 글
[Java] Gradle 버전 확인 및 변경 방법 (2) | 2023.06.27 |
---|---|
[Java] 개발 환경에 따라 각각 환경 파일 구성 방법: application.properties (0) | 2023.06.07 |
[Java/Library] Lombok 이해하고 적용하기 -2 : 심화 및 적용 (0) | 2023.03.27 |
[Java] RESTful API 설계 방법 -2 : 구성하기 (0) | 2023.03.26 |
[Java] RESTful API 설계 방법 -1 : 이해하기 (0) | 2023.03.22 |