프로그래밍/Java
[프로그래밍] Thread with Java
코드몬스터
2023. 9. 9. 17:25
728x90
해당 내용은 인프런의 즐거운 자바 를 보고 정리한 내용입니다!
병렬화 할 때 고려해야 할 것들
- 메모리의 속도
- CPU 캐시 메모리
- 디시크
- 네트워크
- 컨넥션
- 순차적 실행이 병렬실행보다 빠른 경우도 있다. 동시 실행에 따라는 오버헤드가 없고, 단일 CPU 알고리즘은 하드웨어 작업에 더 친화적일 수 있기 때문이다.
암달의 법칙(Amdahl's Law)
병렬 컴퓨팅을 할 경우, 일부 병렬화 가능한 작업들은 사실상 계산에 참여하는 컴퓨터의 개수에 비례해서 속도가 늘어난다. 이러한 경우 암달의 법칙에 의해서 전체 수행시간의 개선 효과는 병렬화가 불가능한 작업들의 비중에 크게 영향을 받게 된다. 아무리 컴퓨터의 개수가 늘어나더라도 속도의 한계는 정해져있다.
병렬 VS 병행
- 병행(Concurrent)은 멀티스레드 프로그래밍을 의마한다.
- 병렬(Parallel)은 멀티코어 프로그래밍을 의미한다.
- 우리가 살펴볼 것은 병행 프로그래밍(동시성 프로그래밍, 멀티스레드 프로그래밍)
프로세스 (Process)
- 각각의 프로세스는 메모리(RAM) 공간에서 독립적으로 존재한다.
- 작업관리자(Window), 활성화상태보기(Mac)에서 확인할 수 있다.
- 각각의 프로세스는 자신만의 메모리 구조를 가지고 모두 같은 구조의 메모리 공간을 가진다.
- 독립적인 만큼 다른 프로세스의 메모리 공간에 접근할 수 없다.
위 이미지와 같이 P1이 실행 되다가 P2가 실행 되고 다시 P1이 실행 될 때, 어디부터 실행되어야 하는지 판단하는 것을 context 스위칭이 발생한다고 한다. 시간이 많이 걸리는 것 오버헤드가 발생한다.
IPC
- 프로세스간에 직접 접근할 수 없기 때문에 특별한 방식이 필요하다. 메일슬록(mailslot), 파이프(pipe) 등이 프로세스 간의 통신 즉, IPC의 예
- 프로세스는 독립적인 메모리 공간을 지니기 때문에 IP를 통하지 않고 통신할 수 없다.
- 프로세스가 여럿이 병렬적으로 실행되기 위해서는 필연적으로 컨텍스트 스위칭이 발생할 수 밖에 없다. 이것을 해결할 수 있는 것이 Thread이다.
Thread
- 스레드는 하나의 프로그램 내에 존재하는 여러 개의 실행 흐름을 위한 모델이다.
- 우리가 생각하는 프로그램이 실행되기 위해서 하나의 실행흐름으로 처리할 수 있지만 다수의 실행흐름으로 처리할 수도 있다.
- 예를 들어 워드 프로그램에서 타이필을 할 때, 맞춤법 검사 등의 여러 실행이 발생한다.
- 프로세스 간의 컨텍스트 스위칭 비용보다 스레드 간의 컨텍스트 스위칭 비용이 더 적게 든다.
※ 컨텍스트 스위칭
동작중인 프로세스가 바뀔 때 프로세스는 현재 자신의 상태(context 정보)를 일단 보존하고, 새롭게 동작 개시하는 프로세스는 이전에 보존해 두었던 자신의 컨텍스트 정보를 다시 복구한다. 이와 같은 현상을 컨텍스트 스위칭 이라고 한다.
스레드 VS 프로세스
- 스레드는 프로세스 안에 존재하는 실행흐름
- 스레드는 stack 영역을 제외한 Heap, Static, Code 메모리 영역을 공유한다
- 스레드가 code 영역을 공유하기 때문에 프로세스 내부의 스레드들은 프로세스가가지고 있는 함수를 자연스럽게 호출할 수 있다.
- 스레드는 IPC 없이도 스레드 간의 통신이 가능하다. A, B 스레드가 통신하기 위해 Heap 영역에 메모리 공간을 할당하고, 두 스레드가 자유롭게 접근할 수 있다.
- 하나의 프로그램이 여러 개의 프로세스를 사용하는 것보다 여러 개의 스레드를 사용하는 것이 프로그래밍 하는 것이 쉽니다.
Thread 클래스를 상속 받아 쓰레드 작성하기
CLASS명 extends thread {
// 오버라이딩을 해줘야 한다.
public void run() {
// 코드 작성
}
}
Thread 작성 방법
- Thread 클래스 상속
main 스레드에서 스레드 1과 스레드 0이 실행되는 것을 아래 이미지로 확인할 수 있다.
// 1. Thread 클래스를 상속 받는다.
public class MyThread extends Thread {
private String str;
public MyThread(String str) {
this.str = str;
}
// 2. run() 메소드를 오버라이딩 한다.
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("---- " + name + "----");
// 3. 동시에 실행시키고 싶은 코드를 작성
// 1초마다 str을 10번 출력하는 프로그램 작성
for (int i = 0; i < 10; i++) {
System.out.println(str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} // for
}
}
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("+");
// 4. thread 는 start() 메소드를 실행한다.
mt1.start();
mt2.start();
System.out.println("end!");
}
}
2. runnable 인터페이스 구현
// 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 + "----");
// 3. 동시에 실행시키고 싶은 코드를 작성
// 1초마다 str을 10번 출력하는 프로그램 작성
for (int i = 0; i < 10; i++) {
System.out.print(str);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} // for
}
}
public class MyThreadExam2 {
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("+");
// 4. thread 인스턴스를 생성하는데, 생성자에 Runnable 인스턴스를 넣어준다.
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
// start() 메소드를 호출한다.
t1.start();
t2.start();
System.out.println("end!");
}
}