
쓰레드란?
쓰레드(thread)는 프로세스 내에서 실행되는 단일 흐름의 실행 흐름입니다.
프로세스는 운영 체제에서 실행되는 프로그램의 인스턴스로, 하나 이상의 쓰레드로 구성됩니다. 각각의 쓰레드는 프로세스 내에서 프로세스로부터 자원을 할당받아 독립적으로 실행되는 가장 작은 실행 단위입니다.
멀티쓰레드
하나의 프로세스 내에 동시에 여러 쓰레드를 실행시키는 것 입니다.
멀티쓰레드의 장점
- CPU의 사용률을 향상시킵니다.
- 자원을 보다 효율적으로 사용할 수 있습니다.
- 사용자에 대한 응답성이 향상됩니다.
- 작업이 분리되어 코드가 간결해집니다.
- 멀티쓰레딩이 가능함으로써 메신저로 파일을 다운로드하면서 채팅을 할 수 있게 됩니다.
멀티쓰레드의 단점
- 동기화(synchronization)이슈
멀티 쓰레드는 프로세스 CODE/DATA/HEAP공간을 공유하고 있기 때문에 공유한 데이터를 읽고 쓰는 과정에서 동기화 이슈가 발생하여 비정상 동작이 일어날수 있습니다. - 교착상태(deadlock)
두 쓰레드가 자원을 점유한 상태에서 서로 상대펀이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태가 될 수 있습니다.
쓰레드의 상태변화
- 쓰레드의 상태
| 상태 | 설명 |
| NEW | 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 |
| RUNNABLE | 실행 중 또는 실행 가능한 상태 |
| BLOCKED | 동기화블럭에 의해서 일시정지된 상태(Lock이 풀릴 때까지 기다리는 상태) |
| WAITING, TIMED_WAITING | 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태. TIMED_WAITING은 일시정지시간이 지정된 경우를 의미한다. |
| TERMINATED | 쓰레드의 작업이 종료된 상태 |

- 쓰레드의 스케쥴링과 관련된 메서드
| 메서드 | 설명 |
| static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다. 지정한 시간이 지나고 나면 자동적으로 다시 실행대기상태가 된다. |
| void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록 한다. 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 기다린다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다. |
| void interrupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기 상태로 만든다. 해당 스레들에서는 InterruptedException이 발생함으로써 일시정지 상태를 벗어나게 된다. |
| void stop() | 쓰레드를 즉시 종료시킨다. |
| void suspend() | 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다. |
| void resume() | suspend()에 의해 일시정지 상태에 있는 쓰레드를 실행대기상태로 만든다. |
| static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신을 실행대기상태가 된다. |
쓰레드 동기화 기술
쓰레드간 공유한 데이터를 읽고 쓸 시에, 쓰레드간 실행 순서에 따라 공유한 데이터를 읽고 쓰는 작업이 누락될 수 있으며, 이로 인해 쓰레드의 비정상 동작을 야기할 수 있습니다. 이러한문제를 막기 위해 여러 쓰레드가 동시에 공유한 데이터의 읽고, 쓰지 못하도록 하는 쓰레드 동기화 기술이 필요합니다.
일반적으로 사용되는 쓰레드 동기화 기술에는 뮤텍스(Mutex), 세마포어(Semaphore), 경쟁 조건을 방지하기 위한 크리티컬 섹션, 락(Lock) 등이 있습니다. 이러한 기술들은 공유 자원에 대한 접근을 조절하여 데이터 일관성과 안전성을 보장하며, 다중 스레드 환경에서의 프로그램 실행을 조율합니다.
뮤텍스(Mutex)와 세마포어(Semaphore)의 차이
Toilet problem
medium.com
뮤텍스(Mutex)
뮤텍스는 쓰레드간 공유한 데이터 접근시, 일정 순간에 하나의 쓰레드만 해당데이터를 접근할 수 있도록 하는 기술입니다.
자바에서는 synchronized 키워드나 ReentrantLock 클래스를 사용해 뮤텍스를 구현할 수 있습니다.
1. synchronized
synchronized를 이용하면 임계영역을 설정할 수 있습니다.
임계영역을 설정하면 lock의 획득과 반납이 모두 자동적으로 이루어지므로 우리는 임계 영역만 설정해주면 됩니다.
- 설정 방법
public synchronized void calcSum(){} //메서드를 임계영역으로 지정
synchronized(객체의 참조변수){} // 특정 객체를 임계영역으로 지정
- 예제
package com.example;
public class ThreadEx {
public static void main(String[] args) {
Runnable r = new RunnableEx22();
new Thread(r).start();
new Thread(r).start();
}
}
class Account2 {
private int balance = 1000;
public int getBalance(){
return balance;
}
public synchronized void withdraw(int money){
if(balance >= money){
try {
Thread.sleep(1000);
} catch (InterruptedException e){
}
balance -= money;
}
}
}
class RunnableEx22 implements Runnable{
Account2 acc = new Account2();
@Override
public void run(){
while (acc.getBalance() > 0){
int money = (int)(Math.random() * 3 +1) * 100;
acc.withdraw(money);
System.out.println("balance: " + acc.getBalance());
}
}
}


2. ReentrantLock
ReentrantLock은 lock() 메소드로 잠금을 얻고, unlock() 메소드로 잠금을 해제합니다. try-finally 블록을 사용하여 예외가 발생해도 잠금을 확실히 해제할 수 있습니다.
- 예제
import java.util.concurrent.locks.ReentrantLock;
class SomeClass {
private final ReentrantLock locker = new ReentrantLock();
public void SomeMethod () {
locker.lock(); // 쓰레드에 락을 겁니다.(동기화의 시작)
try {
동기화내용들...
} catch (어떤예외들) {
해당예외처리...
} finally {
locker.unlock(); // 쓰레드의 락을 풉니다.(동기화 끝지점)
}
}
}
세마포어 (Semaphore)
세마포어는 일정 순간 세마포어에서 정한 동시 접근 쓰레드수만큼만 공유한 데이터를 접근할 수 있도록 하는 기술입니다.
자바에서는 기본적으로 세마포어(Semaphore)를 직접적으로 제공하지는 않습니다. 그러나 java.util.concurrent 패키지에 있는 Semaphore 클래스를 사용하여 세마포어를 구현할 수 있습니다. Semaphore는 동시성 제어를 위한 도구 중 하나로, 특정 리소스에 대한 접근을 제한하는 데 사용됩니다.
- 예제
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 세마포어 생성 (초기 허용 풀 크기 지정)
private static Semaphore semaphore = new Semaphore(3); // 세마포어 허용 풀 크기를 3으로 지정
public static void main(String[] args) {
// 여러 스레드를 생성하여 실행
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Worker());
thread.start();
}
}
static class Worker implements Runnable {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is waiting");
// 세마포어를 이용하여 허용 풀에서 허용을 얻음
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " has acquired a permit");
// 작업 수행 (임계 영역)
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " is releasing the permit");
// 세마포어를 이용하여 허용을 반환
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

'knowledge > computer science' 카테고리의 다른 글
| [네트워크] 쿠키와 세션 (1) | 2024.02.13 |
|---|---|
| [네트워크] TCP와 UDP (1) | 2024.02.06 |
| [DB] 여러 트랜잭션이 경쟁하면 생기는 문제 (0) | 2024.02.01 |
| [OS][Java] 인터럽트(Interrupt) interrupt() 함수 사용하기 (0) | 2024.01.29 |
| [OS] 프로세스 간 통신 방법(IPC: Inter Process Communication) (0) | 2024.01.16 |