두 개의 작업을 하나의 쓰레드로 처리 vs 두 개의 쓰레드로 처리
(a)의 경우에는 한 작업을 마친 후에 다른 작업을 시작하지만 (b)의 경우에는 th1 th2 가 번갈아가면서 작업을 수행한다.
- (b)의 경우는 처리시간이 빠르기에 두 작업이 동시에 일어나는 것처럼 보인다.
(b)의 경우가 당연히 수행시간이 빠를 거라고 생각하지만 오히려 시간이 더 걸린다.
이유는 컨텍스트 스위칭에 시간이 걸리기 때문이다.
컨텍스트 스위칭 : 프로세스 혹은 쓰레드간의 전환을 의미
* 컨텍스트 스위칭(Context Switching)
: CPU 내에 존재하는 레지스터들은 현재 실행 중인 프로세스 관련 데이터들로 채워진다.
실행 중인 프로세스가 변경이 되면, CPU 내 레지스터들의 값이 변경되어야 하는데, 변경되기 전에 이전 프로세스가 지니고 있던 데이터들을 어딘가에 저장해 주어어야 한다.( 이어서 실행하기 위해 ). 그리고 새로 실행되는 프로세스가 아니라면 이전에 실행될 때 레지스터들이 지니고 있던 데이터들을 불러와서 이어서 실행해야 한다. 이 과정이 콘텍스트 스위칭이다.
실행되는 프로세스의 변경 과정에서 발생하는 컨텍스트 스위칭은 시스템에 많은 부담을 준다.
레지스터 개수가 많을수록, 프로세스별로 관리되어야할 데이터 종류가 많을수록 더 부담이 된다.
컨텍스트 스위칭에 소요되는 시간을 줄이려면 저장하고 복원하는 콘텍스트 정보의 개수를 줄여주면 된다.
* 메모리 구조 관점에서 본 프로세스와 쓰레드
: 자식 프로세스가 생성되고 난 다음에는 부모 프로세스가 가진 핸들 테이블은 상속되지만 모든 것(Code/data/heap/stack영역) 이 독립적으로 만들어진다. 이러한 메모리 구조를 지녔기에, 프로세스 간에 데이터를 주고받기 위해서는 IPC(Inter Process Communication)가 필요하다.
하지만 쓰레드를 생성하는 경우 메모리 구조는 다르다.
프로세스가 쓰레드A와 B를 생성한 경우 쓰레드를 생성할 때마다, 해당 쓰레드만을 위한 ThreadStack영역(실행 흐름의 추가를 위한 최소 조건)만이 생성될 뿐, 나머지 영역(Code, Data, Heap)은 부모 프로세스 영역을 공유하고 있다. 쓰레드마다 스택을 독립적으로 할당해준다.
'-'와 ' | '를 출력하는 작업을 한 개의 쓰레드에서 처리하는 예제
public class SingleThread {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i=0;i<500;i++) {
System.out.printf("%s", new String("-"));
}
System.out.println("소요시간 1 : "+ (System.currentTimeMillis() - startTime));
for (int i=0;i<500;i++) {
System.out.printf("%s", new String("|"));
}
System.out.println("소요시간 2 : "+ (System.currentTimeMillis() - startTime));
}
}
'-' 와 ' | ' 를 출력하는 작업을 두 개의 쓰레드에서 처리하는 예제 (main 쓰레드 & MultiThread2 쓰레드)
public class MultiThread {
static long startTime = 0;
public static void main(String[] args) {
MultiThread2 th1 = new MultiThread2();
th1.start();
startTime = System.currentTimeMillis();
for (int i=0;i<500;i++) {
System.out.printf("%s", new String("ㅡ"));
}
System.out.println("소요시간 1 : "+(System.currentTimeMillis()-MultiThread.startTime));
}
}
class MultiThread2 extends Thread{
@Override
public void run() {
for (int i=0;i<500;i++) {
System.out.printf("%s", new String("|"));
}
System.out.println("소요시간 2 : "+(System.currentTimeMillis()-MultiThread.startTime));
}
}
거의 동시에 작업이 완료되었다.
싱글코어로 테스트해볼 수 없다는 점이 아쉽다...
- 싱글 코어로 실행하였을 경우에는 아무리 멀티 쓰레드라 할지라도 하나의 코어가 번갈아가면서 작업을 하는 것이므로 절대 겹치지 않는다.
- 멀티 코어로 실행하였을 경우에는 동시에 두 개의 쓰레드를 처리하므로 겹치는 부분이 발생한다.
자바가 OS에 독립적이라고 하지만 실제로는 OS에 종속적인 부분이 있다.
이 중 하나가 쓰레드이다. - OS의 스케쥴러에 의해 실행 순서와 실행시간이 결정되기 때문이다.
어떠한 경우에 멀티 쓰레드 프로세스가 유용할까?
각각의 쓰레드가 서로 다른 자원을 사용하는 경우에 해당한다.
위의 경우를 예로 들면 첫 번째는 싱글 쓰레드를 이용한 경우이다.
해당 경우에는 점선에 보이는 것처럼 아무것도 하지않는 구간이 발생한다. 이는 A에 대한 사용자의 요청을 기다리는 구간이다.
두 번째 멀티 스레드를 이용한 경우를 보면
싱글 쓰레드를 사용하였을 때 요청을 기다리는 구간에 다른 쓰레드로 B의 요청을 처리한다.
여기서 주의할 점 두 가지
- CPU는 특정 시간의 하나의 프로세스를 처리하는 것
- 각각의 쓰레드는 같은 작업이 아닌 다른 작업을 처리한다는 것이다.
싱글 쓰레드 예시 - 사용자의 입력을 받는 작업 & 숫자를 출력하는 작업
public class MultiThread3 {
public static void main(String[] args) {
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요...");
System.out.println("입력하신 값은 "+input+"입니다.");
for (int i=10;i>0;i--){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
사용자의 입력을 받는 작업 이후에 숫자를 출력하는 작업을 진행하는 것을 볼 수 있다.
멀티 쓰레드 예시 - 사용자의 입력을 받는 작업 & 숫자를 출력하는 작업
public class MultiThread3 {
public static void main(String[] args) {
MultiThread4 multiThread4 = new MultiThread4();
multiThread4.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요...");
System.out.println("입력하신 값은 "+input+"입니다.");
}
}
class MultiThread4 extends Thread {
@Override
public void run() {
for (int i=10;i>0;i--){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
숫자를 입력하는 쓰레드가 실행 중에 값을 입력받는 쓰레드가 포함돼있는 것을 볼 수 있다.
쓰레드의 생명주기
https://12bme.tistory.com/65?category=682904
코드 참조
https://github.com/mike6321/PURE_JAVA/tree/master/Thread
'Java > Java' 카테고리의 다른 글
(JAVA) String.equals() 내부 뜯어보기 (0) | 2020.01.21 |
---|---|
(JAVA) 쓰레드 우선순위 (0) | 2020.01.21 |
(JAVA) 쓰레드의 개요(2) (0) | 2020.01.19 |
(JAVA) 쓰레드의 개요 (1) (0) | 2020.01.19 |
(JAVA) 애노테이션 프로세서 (0) | 2019.12.28 |