10. Java Thread
인프런의 부부 개발단 토토님의 즐거운 자바 강의를 정리한 내용
# 성능 향상시키기
성능이 좋은 프로그램을 만들고 싶다고 하자.
- 예를 들어, 하나의 프로그램에서 위와 같은 작업이 있다.
- B : 병렬화가 가능하지 않은 부분
- 1 - B : 병렬화가 가능한 부분
- 병렬화가 가능한 부분을 두 부분에서 동시에 실행되도록 하면 시간을 아낄 수 있다.
- 병렬화가 가능한 부분을 더 쪼개서 동시에 실행되도록 하면 더 시간을 아낄 수 있다.
- 그러나, 병렬화가 가능하지 않은 부분인 B 때문에 성능을 무한으로 향상시킬 수는 없다.
# 병렬화 시 고려
병렬화 할 때 고려해야할 것들은 아래와 같다.
- 메모리의 속도
- CPU 캐시 메모리
- 디스크
- 네트워크
- 커넥션
- 순차적 실행이 병렬 실행보다 빠른 경우도 있다. 동시 실행에 따르는 오버헤드가 없고, 단일 CPU 알고리즘은 하드웨어 작업에 더 친화적일 수 있기 때문
- 등등 …
암달의 법칙 (Amdahl’s Law)
암달의 법칙(Amdahl’s law)은 암달의 저주로도 불리며, 컴퓨터 시스템의 일부를 개선할 때
전체적으로 얼마만큼의 최대 성능 향상이 있는지 계산하는데 사용된다.
이론(theory)만 많은 컴퓨터과학 분야에서 몇 안되는 법칙(Law)이다.
- 병렬 컴퓨팅을 할 경우, 일부 병렬화 가능한 작업들은 사실상 계산에 참여하는 컴퓨터의 개수에 비례해서 속도가 늘어남
- 이러한 경우 암달의 법칙에 의해 전체 수행시간의 개선 효과는, 병렬화가 불가능한 작업들의 비중에 크게 영향을 받게 됨
- 즉, 아무리 컴퓨터의 개수가 늘어나더라도 속도의 한계는 정해져있다는 것
# 병행 vs 병렬
- 병행(Concurrent)은 멀티스레드 프로그래밍을 의미
- 병렬(Parallel)은 멀티코어 프로그래밍을 의미
- 우리가 살펴볼 것은 병행 프로그래밍 (동시성 프로그래밍, 멀티스레드 프로그래밍)
- 활성 상태 보기를 보면 이렇게 프로세스, 스레드 등을 확인할 수 있다.
- 각각의 프로세스들은 자신만의 메모리 영역을 확보한 채로 실행되고 있다.
# Context Switching
- 적절한 시간만큼 쪼개서 프로세스가 실행된다.
- 컴퓨터는 굉장히 빠르기 때문에 P1~P4가 마치 “동시에 실행되는 것처럼” 보인다.
- 실제로 동시에 실행되는 것은 아니다.
- 예를 들어, 프로세스 1 실행동안 다른 프로세스는 멈춰있다.
- 만약, P1이 실행되다가 잠시 멈췄다가 P2가 실행되는 상황이라고 하자. 그렇다면 나중에 다시 P1을 실행하려면, 어디까지 실행이 되었었는지, 메모리는 어디 부분을 사용하고 있었는지를 기억하고 있어야 할 것이다. 그래야 나중에 이어서 실행할 수 있으니까.
- 이런 부분을 Context Switching이 발생한다고 한다.
- 이 과정에서 시간이 많이 걸린다. 오버헤드가 발생한다.
# Process
- 각각의 프로세스는 메모리 공간에서 독립적으로 존재
- 각각의 프로세스는 자신만의 메모리 구조를 가짐 (스택, 힙, BSS, Text)
- 프로세스 A, B, C가 있을 경우 각각 프로세스는 모두 같은 구조의 메모리 공간을 가짐
- 독립적인만큼 다른 프로세스의 메모리 공간에 접근할 수 없음
- 그렇다면, 프로세스끼리 어떻게 통신할까? -> IPC
# IPC
IPC (Inter-Process Communication) : 프로세스간 통신
- 프로세스 A에서 프로세스 B를 직접 접근할 수 없기 때문에, 프로세스 간의 통신을 하는 특별한 방식이 필요하다. 메일슬록(mailslot), 파이프(pipe) 등이 바로 프로세스 간의 통신, 즉, IPC의 예시이다.
- 프로세스는 독립적인 메모리 공간을 지니기 때문에 IPC를 통하지 않고 통신할 수 없다.
- 프로세스가 여럿이 병렬적으로 실행되기 위해서는 필연적으로 Context Switching이 발생할 수 밖에 없다. 이것을 해결할 수 있는 것이 Thread이다.
- 스레드도 컨택스트 스위칭이 없는 것은 아니다.
- 하지만, 프로세스의 컨택스트 스위칭 비용보다 스레드의 컨택스트 스위칭 비용이 더 작아서 스레드가 더 유리하다.
# Thread
- 스레드는 하나의 프로그램 내에 존재하는 여러 개의 실행 흐름을 위한 모델
- 우리가 생각하는 프로그램이 실행되기 위해서 하나의 실행흐름으로 처리할 수도 있지만, 다수의 실행흐름으로 처리할 수도 있음
- 스레드는 프로세스와 별개가 아닌 프로세스를 구성하고 실행하는 흐름
- 스레드도 Context Switching이 발생
메모리 공간에서의 스레드
- 하나의 프로세스가 가지고 있는 메모리를 여러 개의 스레드가 공유한다.
- 프로세스와 프로세스 간 전환보다 스레드와 스레드 간 전환의 비용이 당연히 적다.
# Thread vs Process
- 스레드는 프로세스 안에 존재하는 실행 흐름
- 스레드는 프로세스의 heap, static, code 영역 등을 공유
- 스레드는 stack 영역을 제외한 메모리 영역은 공유
- 스레드가 code 영역을 공유하기 때문에, 프로세스 내부의 스레드들은 프로세스가 가지고 있는 함수를 자연스럽게 모두 호출할 수 있음
- 스레드는 IPC 없이도 스레드 간 통신 가능
- A, B 스레드는 통신하기 위해 heap 영역에 메모리 공간 할당, 두 스레드가 자유롭게 접근 가능
- 스레드는 프로세스처럼 스케쥴링의 대상이다. 이 과정에서 컨택스트 스위칭이 발생
- 하지만, 스레드는 공유하고 있는 메모리 영역 덕분에 컨택스트 스위칭 때문에 발생하는 오버헤드가 프로세스에 비해 적음
- 동작 중인 프로세스가 바뀔 때 프로세스는 자신의 상태(context 정보)를 일단 보존한 후, 새롭게 동작 개시하는 프로세스는 이전에 보존해 두었던 자신의 context 정보를 다시 복구한다. 이와 같은 현상을 Context Switching이라 한다.
- 스레드의 컨택스트 정보는 프로세스보다 적기에 스레드의 컨택스트 스위칭은 가볍게 행해지는 것이 보통
- 하지만, 실제로 스레드와 프로세스의 관계는 JVM 구현에 크게 의존
- 하지만, 스레드는 공유하고 있는 메모리 영역 덕분에 컨택스트 스위칭 때문에 발생하는 오버헤드가 프로세스에 비해 적음
- 참고로, 플랫폼이 같아도 JVM의 구현방법에 따라 프로세스와 스레드의 관계는 달라질 수 있음
무조건적으로 멀티 스레드 프로그래밍이 더 낫다는 의미는 아니다. 여러 스레드가 하나의 메모리를 공유해서 사용하는 경우에, 자원을 획득하기 위한 스레드 간 경쟁이 발생한다.
- 메모리가 많고, 컴퓨터의 속도가 빠른 경우에는 여러 개의 프로세스를 이용하여 실행하는 것이 여러 개의 스레드보다 유리할 수 있다.
멀티스레드(Multi Thread) 실행 방식
- main 메서드부터 시작되어 흘러가는 하나의 흐름을 Main Thread라고 한다.
- Main 흐름 안에서 새로운 스레드를 발생시켜서 동시에 여러 흐름이 흐르게 할 수 있다.
# Java Thread 생성
Java에서 스레드를 생성하는 방법은 2가지가 있다.
- Thread 클래스 상속받기
- Runnable 인터페이스 구현하기
# Thread 클래스 상속
- Thread 클래스를 상속받는다.
run()
메서드를 오버라이딩 한다.start()
메서드로 시작한다.
- Thread 클래스를 상속받아 스레드를 작성할 수 있음
- Thread의
run()
메서드를 상속받는 클래스에서 반드시 오버라이딩 해야한다. start()
메서드가 굉장히 중요하다.- Thread가 실행될 준비를 해주고,
run()
메서드를 실행시키면서 스레드 흐름이 하나 더 생기게 되는 것이다.
- Thread가 실행될 준비를 해주고,
|
|
|
|
예시
|
|
|
|
- 모든 스레드가 종료될 때 프로그램이 종료된다.
# Runnable 인터페이스 구현
- Runnable 인터페이스 구현
run()
메서드 오버라이딩- Thread 인스턴스를 생성할 때, 생성자에 Runnable 인스턴스 넣기
- Thread가 가지고 있는
start()
메서드 호출
- Runnable 인터페이스를 구현하면
run()
메서드를 반드시 오버라이딩 - 근데, Runnable 인터페이스에는 스레드가 실행될 준비를 해주는
start()
메서드가 없다.- 그래서 만든 클래스를 스레드가 가지도록 해야 한다.
|
|
|
|
|
|
|
|