학습할 것 (필수)
- Thread 클래스와 Runnable 인터페이스
- 쓰레드의 상태
- 쓰레드의 우선순위
- Main 쓰레드
- 동기화
- 데드락
✔️ 개념 정리
🌟 Process (프로세스)
: 운영체제로 부터 필요한 메모리(자원)를 할당받아 실행중인 프로그램
- 코드 / 데이터 / 스택 / 힙 메모리 영역으로 구성


🌟Thread (쓰레드)
: 프로세스의 자원을 이용해 작업을 수행하는 프로세스를 처리하는 일꾼 같은 역할. (종류 : 싱글 쓰레드 / 멀티 쓰레드)
- 특징 : 쓰레드는 각자의 개별의 스택을 가지고 프로세스의 전역 메모리 공간을 공유하여 실행 된다.
| 장점 | 단점 |
| - 사용자 응답성 향상 (병렬 처리, 동시 실행) - 자원 공유 효율 : 번거로운 프로세스간 자원공유를 위한 ipc작업이 없음 |
- 하나의 스레드에 문제가 있어도 모두 영향을 받는다. - 쓰레드가 많으면 context switching 의 부화로 성능 저하 : 쓰레드가 많으면 모든 쓰레드를 스케쥴링해함. |

멀티 프로세스는 자원에 문제가 생겨도 다른 프로세스는 영향을 받지 않지만 멀티 쓰레드의 경우에는 하나의 쓰레드에 문제가 생기면 모든 프로세스에 영향이 간다.
인터뷰에서 가장 자주 나오는 운영체제에 대한 면접 질문!!!
📍 프로세스와 쓰레드의 차이를 설명하세요.
| 프로세스 | 쓰레드 |
| OS로부터 자원을 할당받아 실행 - 독립적 - 자신만의 주소 영역을 가짐 - 프로세스간 IPC 기법을 통신 |
- 프로세스 로부터 자원을 할당받아 실행 * 쓰레드는 개별의 스택을 가지고 프로세스의 전역 메모리 공간을 공유한다. - 하나의 프로세스 안에 must! 하나 || 여러 멀티 쓰레드 생성 가능 - 프로세스의 서브셋 - 주소영역 공유 |
🌟Multi-Tasking (Multi-Processing)
: 운영체제가 지원하는 여러 프로세스가 동시에 실행될수 있는 것
- 태스크 : 운영체제에서 처리하는 작업의 단위 (process의 확장 개념)
- 스케쥴링에 의해 task 여러개를 번갈아가며 수행하여 동시에 처리하는것 처럼 보인다.

🌟 Parallel-Programming (병렬 프로그래밍)
: 두 개 이상의 쓰레드가 동시에 병렬로 수행하는 경우
*. Concurrent Programming
: 하나의 프로세서/쓰레드 가 입출력 작업 종료를 대기할 동안 다른 프로그램을 수행할 수 있도록 시분할 시스템을 이용하요 병렬로
처리하여 수행하는 것
- 자원 낭비를 막기 위함.
- "concurrent" refers to doing multiple tasks at the same time
- ex. within Java, running multiple threads are supported for concurrent programming within a single program

# 참고 : www3.ntu.edu.sg/home/ehchua/programming/java/j5e_multithreading.html
🌟 Multi-Threading (멀티 쓰레딩)
: 하나의 프로세스 내에 같은 자원을 공유하는 여러개의 쓰레드(실행 단위)가 수행
- CPU는 한번에 하나의 작업만을 수행할 수 있으므로 실제로는 동시에 처리되는 작업의 개수와 일치 (아주 짧은 시간동안 여러 작업을 번
갈아 수행하여 동시에 실행되는 것 처럼 보이게 한다.)
- 병렬 처리
| 장점 | 단점 |
| - CPU 사용률 향상 - 자원을 효율적으로 이용 가능 - 코드가 간결해짐 (코드를 짜기 나름) |
여러 쓰레드가 같은 프로세스의 자원을 공유하여 작업을 하기 때문에 동기화, 교착상태같은 문제 발생 가능 |
- 주의: 멀티 쓰레드라고 무조건 좋은건 아니고 상황에 따라 낮은 성능을 보일 수도 있다.
📍 여기서 잠깐! 그러면 여러개의 프로세스와 여러개의 쓰레드 차이는??
| 멀티 태스킹 | 멀티 쓰레드 |
| OS에서 지원하는 독립된 메모리를 가져 자원 공유가 이루어지지않는 장점이 있다. 하지만 OS에 부담을 줄 수 있다. | 쓰레드끼리 자원 공유가 가능하며 프로그래밍을 통해 구현 |
🌟 DeadLock (교착상태)
: 2 개 이상의 프로세스가 자원을 점유한 상태에서 다른 프로세스의 작업이 끝나기만을 기다리며 작업을 더 이상 진행하지 못하는 상태

🌟 Semaphore & Mutex
: 임계 구역 (Critical Section) 에 대한 접근을 막기 위한 Locking 매커니즘 필요
- semphore (깃발) : 공유자원의 개수를 나타내는 변수
- counting semaphore : 도메인 제한이 없어 공유 자원의 개수를 count하고 사용시 하나씩 감소하고 반환하면 세마포어++
- binary sempahore: 공유자원이 하나이기 때문에 key를 가지거나 안가지는 것으로 분류
| Semaphore | Mutex (binary sempahore) |
| signal mechanism | locking mechanism |
| 공유된 자원을 여러 프로세스가 접근하려고 하는 것을 막음. - 임계구역에 여러 쓰레드가 들어갈 수 있음 - counter을 두어서 동시에 리소스를 접근 할 수 있는 허용 가능한 쓰레드 수를 제어 |
공유된 자원을 여러 스레드가 접근하는 것을 막음. - 임계구역에 하나의 스레드만 들어감 |

operations: wait() / signal()
shared data 를 쓰려고 하는데 공유자원이 이미 사용중이라면 0 으로 사용할 수 없음 으로 wait() 하고 signal() 을 주고 만약 공유 자원이 비워 있다면 1 이라는 signal() 으로 공유자원이 이용가능하다는 signal()을 주어야 함.
🌟 Daemon Thread
: 주 작업 쓰레드를 돕는 보조적인 역할을 하는 쓰레드 . 주 쓰레드가 종료되면 강제적으로 같이 종료
- ex. JVM 에서 가비지 컬렉터, 워드 프로세서에서 자동저장, 회원 자동 생산, 미디어 플레이어의 음악 재생
- 데몬 쓰레드의 우선순위는 자신을 생성한 쓰레드와 같은 우선순위를 가지게 됨.
✔️ Thread 클래스와 Runnable 인터페이스
: 자바에서 쓰레드를 생성하는 방법은 두가지 방법이 있습니다.
** 쓰레드 구현 = 쓰레드를 통해 작업할 내용을 run() 안에 지정
1. Thread 클래스 상속
2. Runnable 인터페이스
🌟 Thread 클래스 상속
: 자식 클래스는 실행 메소드 재정의해야 인스턴스 할당하고 실행 가능
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread클래스를 상속받아 만든 쓰레드 입니다.");
}
}
📍 Thread 클래스
✔️ 필드
: 쓰레드의 우선순위에 대한 상수 필드 (우선순위, 우선순위 최소값, 우선순위 최대값)
✔️생성자
- gname = 스레드 이름 자동생성
- name = 인자 (매개변수에 들어갈 값) 새로운 스레드 이름
- targe : run()메소드가 호출될 객체
- group : 생성할 스레드를 설정할 스레드 그룹
- stacxkSize : 새로운 스레드 스택 사이즈 (할당할 주소 공간-바이트수)
✔️메소드
- public void run() : 쓰레드가 실행되면 run() 메소드를 호출하여 작업
- 메소드 수행이 종료되면 호출 스택이 비워지면서 생성된 호출 스택도 소멸됨.
- public synchronized void start() : 쓰레드를 실행시키는 메소드. start 메소드가 호출되면 실행 대기 상태에 있다가 자신의 차례가 되면 실행됨.
- IllegalThreadException : 두번이상 호출되면 에러. 한번만 호출 가능
- start 메소드를 호출해서 쓰레드 실행. run()은 쓰레드를 실행하는 것이 아닌 메소드를 호출하는 역할.

- 쓰레드의 스케쥴링과 관련된 메소드 (참고)

🌟 Runnable Interface 구현
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("Runnable interace로 구현한 쓰레드");
}
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
📍 Thread 클래스를 상속(extends) 받으면 다른 클래스를 상속 받을 수 없기 때문에 Runnable 인터페이스를 구현하는 방법이 일반적
- reusability & high code consistency 유지 -> 객체지향적인 방법
✔️ 쓰레드의 상태
: 구조 -> New/ Runnable / Blocekd / Waiting, Timed_waiting / Terminated
- 정교한 스케쥴링을 통해 프로세스에게 주어진 자원을 여러 쓰레드가 낭비없이 효율적으로 사용하도록 프로그래밍
: 쓰레드 객체가 start() 를 호출하면 쓰레드가 실행되는 것 처럼 보이지만 실행 대기 상태가 된다. 스케쥴링으로 선택된 쓰레드가 CPU를
점유하고 run() 실행. (New -> Runnable -> Running -> Runnable -> Running -> Terminated)
실행 중이 쓰레드가 Runnable 상태에 들어갔을때 CPU 는 다른 쓰레드를 선택하여 실행한다.

1. 쓰레드를 생성하고 start() 를 호출하면 스케쥴링 상 본인의 차례가 되면 run() 실행
- 대기열은 Queue 구조로 FIFO
2. 본인 차례에 실행
3. 할당된 실행 시간이 다되거나 yield() - 양보 메소드를 만나면 다시 실행 대기 상태 (Runnable)이 되고 스케쥴링 상 지정된
다음 쓰레드가 실행 상태가 됨.
4. 실행 중, suspend() / sleep()/ wait() / join()/ I/O Block에 의해 일시정지 상태가 될 수 있음.
- I/O block은 입출력 작업에서 발생하는 지연 상태 : 사용자 입력 받는 경우
5. 일시정지 시간이 끝나거나 notify() / resume() / interrupt() 가 호출되면 다시 대기열에 저장되어 차례기다리는 상태로 변경
6. 실행을 모두 마치거나 stop() 호출시 쓰레드 소멸
✔️ 쓰레드의 우선순위
: 쓰레드 클래스에는 우선순위라는 맴버변수 속성을 가지고 있는데 이 우선순위에 따라 쓰레드가 얻는 실행 시간이 달라진다.
- 작업의 주요도에 따라 우선순위를 다르게 지정하여 특정 쓰레드가 더 많은 작업 시간을 갖도록 한다.
- 쓰레드가 가질수 있는 우선순위의 범위는 1-10이며 숫자가 높을수록 우선순위가 높음.
- 메인 메소드 수행하는 쓰레드는 우선순위가 5 이므로 그 안에 생성하는 쓰레드는 default = 5
| final static int MIN_PRIORITY = 1 | 우선 순위의 최소값 |
| final static int NORM_PRIORITY = 5 | 기본 우선순위 값 |
| final static int MAX_PRIORITY = 10 | 우선 순위의 최대값 |
| setPriority(int newPriority) | 쓰레드의 우선순위를 지정한 값으로 변경 |
| getPriority() | 쓰레드의 우선순위 반환 |
✔️ Main 쓰레드
: JVM 은 하나의 프로세스이고 Java Application은 기본적으로 하나의 메인 쓰레드를 가지고 있음.
- 모든 쓰레두눈 메인 쓰레드로부터 생성됨.


✔️ 동기화
: 시간에 딱딱 맞춰서 일을 진행하여 다른 두시간을 하나로 합치는 행위 -- 종류 : 동기식 (synchorinzed) / 비동기식 (asynchorinzed)
동기식 : 멀티 쓰레드 환경에서 여러 쓰레드가 같은 프로세스의 자원을 공유하여 작업하다보니 영향을주기 때문에 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 하는 것 ( 중간에 interrupt 불가능)
<--> 비동기식 (쓰레드 진행 중에도 다른 쓰레드가 방해하여 끊을 수 있다) - ex. messaging
📍 이해를 돕기위한 간단한 예시,
동기 -> 라면을 끓이는 중에는 다른 일 멀티테스킹 불가함.
비동기 -> 라면 물이 끓는동안 청소기도 돌리고 다시 돌아와서 라면과 스프를 넣고 끓는동안 빨래도 넣어두고 다시 돌아온다.
✔️ 동기식
: 공유 데이터를 사용하는 코드를 임계 구역 (critical section) 으로 지정하고 lock을 가지고 있는 쓰레드만 이 영역 내의 코드를 수행할수 있도록 지정해준다. lock 을 반납해야만 다른 쓰레드가 반납된 lock을 획득하여 코드 수행
** 임계 구역 : 공유 자원 점근 순서에 따라 실행 결과가 달라지는 프로그램 영역
- 임계구역 문제 해결 조건
- 상호 배제 (mutual exclusion)
- 한정 대기 (bounded waiting)
- 진행의 융통성 (progress flexibility)
🌟 Synchornized Block
: 프로그램의 성능을 위해 메소드 전체에 락을 걸기보다 블럭으로 임계구역 최소화해서 보다 효율적인 프로그램 생성
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
// 1. 메소드 전체를 임계 구역으로 지정
// : 메소드가 호출된 시점부터 lock을 얻어서 작업을 하고 메소드가 종료되면 lock반환
public synchorinzed void withdraw(int money) {
if(balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
balance -= money;
}
}
}
🌟 wait() / notify() 의 역할
: 동기화를 통해 공유 데이터를 보호할 수 있지만 특정 쓰레드가 객체의 락을 너무 오래동안 가지지 않는것도 중요
- 특정 쓰레드의 락이 반환되기를 너무 오래 기다리면 작업이 원활하지 않다. :(
그러므로, 임계구역에서 lock 이 필요한 상황이 아니라면 wait() 을 통해 락을 반납하고 다른 쓰레드에게 작업 수행 권한을 넘겨주고 작업을
다시 진행할때 notify() 를 통해서 락을 받아 다시 작업을 진행 합니다.
| wait() | 락을 반납하고 기다리는 상태로 변경 |
| notify() | 대기중인 쓰레드 중 임의의 쓰레드에게 lock 을 얻을 수 있는 상태로 변경 |
| notifyAll() | 기다리고 있는 모든 객체에게 lock을 얻을 수 있는 상태로 변경 - waiting pool 대기중이 쓰레드 해당 |
🌟 Starvation (기아상태) & Race Condition (경쟁 상태)
✔️ starvation : 오래동안 lock 을 얻지 못하고 기다리는 형상
- 우선순위에서 밀려 특정 쓰레드가 계속 기다리는 상태로 남아있는 경우
✔️ Race Condition : 모든 쓰레드가 notifyAll() 을 통해 통지 받기에 불필요한 쓰레드까지 락을 얻으려고 경쟁하는 현상
->> 해결 방법 : Lock 과 Condition 이용하여 선별적인 통지
🌟Lock 클래스
| ReetrantLock | 재진입이 가능한 lock ; 가장 일반적인 배타(exclusive) lock |
| ReentrantReadWriteLock | 읽기에는 공유적이고 쓰기에는 배타적인 lock - 일기를 위한 lock 과 쓰기를 위한 lock 을 제공 - 읽기 lock 에 걸린 상태에서 동시에 여러 쓰레드가 읽기 lock 을 얻는것은 가능하지만 다른 쓰기 lock 을 거는 것은 불가능 (읽기 - 읽기 lock / 쓰기 - 쓰기 lock) |
| StampedLock | ReentrantReadWriteLock 에 낙관적인 lock 기능 추가 - 낙관적 읽기 락 (optimistic reading lock) 기능 추가 - 낙관적 읽기 lock 은 쓰기 lock 에 의해 풀림. |
int getBalance() {
long stamp = lock.tryOptimisticRead(); // 낙관적 읽기 lock
int curBalance = this.balance; // 공유 데이터인 balance 읽어옴.
if(lock.validate(stamp)) { // 쓰기 lock 에 의해 낙관적 읽기 lock 풀렸는지 확인
stamp = lock.readLock(); // lock이 풀렸으면, 읽기 lock 을 얻으려고 기다린다.
try{
curBalance = this.balance; // 공유 데이터 다시 읽어오기
} finally {
lock.unlockRead(stamp); // 읽기 lock 풀기
}
}
return curBalance; //낙관적 읽기 lock 이 풀리지 않았으면 읽어온 값 반환
}
🌟Condition
: wait() / notify() -쓰레드 구분하지 못함-의 단점인 경쟁상태 해결
- waiting pool 에서 세분화하여 쓰레드의 종류에 따라 구분하여 넣음 (경쟁 상태 발생 가능성을 낮출 수 있음)
private ReentrantLock lock = new ReentrantLock(); //lock 생성
//lock condition 생성
private Condition forBanker = lock.newCondition();
private Condition forCustomer = lock.newCondition();
| Object | Condition |
| wait() | await()/ awaitUninterruptibly() |
| wait(long timeout) | boolean await(long time, TimeUnit unit) / awaitNanos(nanosTimeout) / boolean awaitUntil(Date deadline) |
| notify() | signal() |
| notifyAll() | signalAll() |
** notify() + wait() : 대상이 무엇인지 보이지 않음.
await() + signal() : 대기와 통지의 대상이 명확하게 보임. 👍👍
🌟 Fork() / Join()
- fork() : 쓰레드 풀의 작업큐에 넣기 / asynchronous method
* 호출만 하고 결과를 기다리지 않음 -- 기다리지 않고 다음줄 명령어 실행
- join() : 해당 작업의 수행 끝을 기달렸다가 수행이 끝나면 그 결과를 반환 / synchronous method
✔️ DeadLock (교착 상태)

예시 :
다섯명의 사람이 원형 테이블을 공유하는데 테이블 중앙에 밥이 있다. 서로 소통하지 않고 왼쪽에 있는 젓가락과 오른쪽에 있는 젓가락으로 젓가락을 2개 집었을 경우 식사를 할 수 있다. 식사를 마치면 젓가락 2개를 모두 놓고 다시 생각하고 누가 쓰고 있을 때는 젓가락을 사용하지 못한다.
- 교착 상태 발생하는 4 가지 조건
| 상호 배제 (mutual exclusion) | 한번에 한개의 프로세스만 공유 자원을 사용할수 있다. 자원을 공유하지 못하면 교착 상태가 발생합니다. 자원은 배타적인(exclusive) 자원으로 임계구역에서 보호되어 다른 프로세서(쓰레드)가 동시에 사용할 수 없습니다. |
| 비선점 (non-preemption) | 다른 프로세스에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없습니다. 그러므로 상대가 자원을 놓을때까지 기다려야 함으로 교착상태 발생 |
| 점유와 대기 (hold and wait) | 자원 하나를 잡은 상태에서 다른 자원을 추가로 점유하기 위해 기다리면 교착 상태 발생 |
| 원형 대기 (circular wait) | 자원을 요구하는 방향이 원을 이루면 양보가 불가능하기 때문에 교착 상태 발생 |
- 교착 상태 해결 방법
| 교착 상태 예방 | 네가지 조건중 하나를 없앱니다. |
| 교착 상태 회피 | 교착 상태가 발생하지 않는 수준으로 자원 할당 |
| 교착 상태 검출 | 자원 할당 그래프를 사용해 교착 상태 발견 |
| 교착 상태 회복 | 검출한 후 해결 |
- 세마포어 해결방법 (Semaphore Solution)
| semaphore chopstick[5] | 젓가락을 하나의 세마포어로 표현한다. |
| wait() | 젓가락을 집으려고 한다 |
| signal() | 젓가락을 놓는다. |
while(true) {
wait(chopstick[i]);
wait(chopstick[(i+1) % 5]);
// eat
signal(chopstick[i]);
signal(chopstick[(i+1) % 5]);
// think
}
-- 교착 상태 야기 가능성 있음
1. 최대 4명의 사람만 테이블에 앉게 한다.
2. 한 사람이 두개를 모두 집을수 있을때만 젓가락을 집도록 한다.
3. 비대칭 해결안 사용
- 홀수번째 사람은 왼쪽 젓가락부터 집고 짝수는 오른쪽 젓가락부터 집는다.
'🌈 Java > Back to Basic 101' 카테고리의 다른 글
| 3주차 과제: 연산자 (0) | 2021.03.30 |
|---|---|
| 자바 기초 정리: Call by value & Call by reference (0) | 2021.03.03 |
| 자바 기초 정리: @Overloading vs. @Overriding (0) | 2021.03.03 |
| 자바 기초 정리: ArrayList vs. LinkedList (0) | 2021.03.03 |
| 자바 기초 정리: IF vs. Switch (0) | 2021.03.03 |