| 항목 | 프로세스 (Process) | 스레드 (Thread) |
|---|---|---|
| 정의 | 실행 중인 프로그램 | 프로세스 내 작업 단위 |
| 메모리 | 독립된 메모리 공간 사용 | 프로세스의 메모리 공유 |
| 생성 비용 | 상대적으로 큼 (자원 별도 필요) | 가벼움 (자원 공유) |
| 안정성 | 한 프로세스 종료돼도 다른 프로세스에 영향 없음 | 한 스레드 오류 시 전체 프로세스 영향 가능성 있음 |
| 예시 | 크롬, VSCode, Discord 등 실행 프로그램 | 크롬에서 여러 탭을 동시에 여는 구조 |
💡 스레드는 프로세스의 작업 단위로, 여러 스레드가 하나의 프로세스 내에서 CPU를 공유하면서 동시에 실행될 수 있습니다.
컨텍스트 스위칭(Context Switching)은 운영체제가 CPU를 A 스레드에서 B 스레드로 전환할 때 발생하는 상태 저장/복원 작업입니다.
스레드마다 레지스터, 프로그램 카운터 등의 상태(컨텍스트)를 가짐
A 스레드의 상태를 저장하고 B 스레드의 상태를 불러오는 과정
이 과정에서 CPU 리소스를 소비하므로 너무 잦으면 오버헤드 발생
💡 멀티태스킹이 가능하게 해주는 기술이지만, 너무 잦으면 성능 저하의 원인이 됩니다.
멀티스레드 환경에서는 여러 스레드가 공유 자원(예: 전역 변수, 파일, DB)에 동시에 접근할 수 있기 때문에,
데이터 충돌이나 상태 불일치(race condition)가 발생할 수 있습니다.
class ThreadEx21 {
public static void main(String args[]) {
Runnable r = new RunnableEx21();
new Thread(r).start();
new Thread(r).start();
}
}
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
public void calc(int data){
if(balance >= data) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= data;
}
}
}
class RunnableEx21 implements Runnable {
Account acc = new Account();
public void run() {
while(acc.getBalance() > 0) {
int randomData = (int)(Math.random() * 3 + 1) * 100;
acc.calc(randomData);
System.out.println("balance:"+acc.getBalance());
}
} // run()
}
실행 결과:
balance:800
balance:700
balance:500
balance:500
balance:200
balance:0
balance:-200
위 코드를 실행하면 balance값이 음수가 된다.
한 쓰레드가 calc()의 if문에서 조건식을 만족해서 data를 뺀순간, 다른 스레드에게 제어권이 넘어가서 다른 쓰레드가 또 if문의 조건식을 만족해서 data를 빼버리는 순간이 온다.
그래서 결국 balance값은 음수가 되서 프로그램이 종료된다.
-> calc() 함수에 synchronized키워드를 붙여서 한 번에 하나의 스레드만 임계 영역을 실행할 수 있게 만듭니다.
임계 영역(Critical Section)은 둘 이상의 스레드가 동시에 접근할 경우 문제를 일으킬 수 있는 코드 구간입니다.
...
public synchronized void calc(int data){
if(balance >= data) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= data;
}
}
...
실행결과
balance:900
balance:700
balance:400
balance:200
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:100
balance:0
balance:0
이렇게 해줌으로써, calc()가 호출되면 calc()가 종료되어 lock이 반납될때까지 다른 쓰레드는 calc()를 호출하더라도 대기 상태에 머무르게 된다.
💡 동기화는 여러 스레드가 동시에 공유 자원에 접근할 때 데이터 정합성을 보장하기 위해 필요합니다.
데드락(Deadlock)은 두 개 이상의 스레드가 서로가 가진 자원을 기다리며 무한 대기 상태에 빠지는 현상입니다.
예시:
class Shared {
synchronized void methodA(Shared other) {
System.out.println(Thread.currentThread().getName() + " calls methodA");
try { Thread.sleep(100); } catch (InterruptedException e) {}
other.methodB(this); // 여기서 데드락 가능
}
synchronized void methodB(Shared other) {
System.out.println(Thread.currentThread().getName() + " calls methodB");
try { Thread.sleep(100); } catch (InterruptedException e) {}
other.methodA(this); // 여기서 데드락 가능
}
}
public class DeadlockExample {
public static void main(String[] args) {
Shared s1 = new Shared();
Shared s2 = new Shared();
new Thread(() -> s1.methodA(s2), "Thread-1").start();
new Thread(() -> s2.methodB(s1), "Thread-2").start();
}
}
→ 서로가 서로를 기다리며 무한 대기 → 데드락 발생
✅ 데드락 발생 4가지 조건 (전부 충족 시 발생)
상호 배제(Mutual Exclusion)
– 자원은 한 번에 하나의 스레드만 사용 가능
점유 대기(Hold and Wait)
– 자원을 점유한 상태에서 다른 자원을 기다림
비선점(No Preemption)
– 자원을 강제로 뺏을 수 없음
환형 대기(Circular Wait)
– 자원을 서로 물고 물고 있는 순환 대기 상태
방지 방법
1. 자원 획득 순서를 일관되게 정함
class Shared {
void methodA(Shared other) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " acquired lock on this");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (other) {
System.out.println(Thread.currentThread().getName() + " acquired lock on other");
}
}
}
void methodB(Shared other) {
// methodA와 동일한 순서로 락 획득
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " acquired lock on this");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (other) {
System.out.println(Thread.currentThread().getName() + " acquired lock on other");
}
}
}
}
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 작업
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
💡 데드락은 리소스를 차례로 요청하다가 서로 물고 물려서 아무 것도 못하는 상태로, 이를 방지하려면 자원 접근 순서나 타임아웃 설정이 중요합니다.