프로세스(Process)란?
cpu에 의해 메모리에 올라가 실행중인 프로그램
즉, 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말한다
프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성된다
프로세스 안에서 실질적으로 작업을 실행하는 단위
하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행하는 것
멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유하므로, 시스템 자원의 낭비가 적다
또한, 하나의 스레드가 작업을 할 때 다른 스레드가 별도의 작업을 할 수 있어 사용자와의 응답성도 좋아진다
자바에서 스레드 구현 방법은 2가지가 있다
모두 run() 메소드를 오버라이딩한다
1. Runnable 인터페이스 구현
public class MyThread implements Runnable {
@Override
public void run() {
// 수행 코드
}
}
2. Thread 클래스 상속
public class MyThread extends Thread {
@Override
public void run() {
// 수행 코드
}
}
두가지 방법은 인스턴스 생성 방법에 차이가 있다
1. Runnable 인터페이스 구현
2. Thread 클래스 상속
class ThreadWithClass extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName()); // 현재 실행 중인 스레드의 이름을 반환함.
try {
Thread.sleep(10); // 0.01초간 스레드를 멈춤.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadWithRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()); // 현재 실행 중인 스레드의 이름을 반환함.
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Thread01 {
public static void main(String[] args){
ThreadWithClass thread1 = new ThreadWithClass(); // Thread 클래스를 상속받는 방법
Thread thread2 = new Thread(new ThreadWithRunnable()); // Runnable 인터페이스를 구현하는 방법
thread1.start(); // 스레드의 실행
thread2.start(); // 스레드의 실행
}
}
스레드의 실행은 run() 호출이 아닌 start() 호출로 해야한다.
스레드를 이용한다는 건 JVM이 다수의 콜 스택(call stack)을 번갈아가며 일처리를 하고 사용자는 동시에 작업하는 것처럼 보여주는 것이다.
* 콜 스택(call stack, 호출 스택) : 메소드 수행에 필요한 메모리가 제공되는 공간. 실질적인 명령어들을 담고 있는 메모리로, 하나씩 꺼내서 실행시키는 역할을 한다
run() 메소드를 사용한다면 main()의 콜 스택 하나만 이용하는 것으로 스레드를 활용하는 것이 아닌, 그냥 스레드 객체의 run이라는 메소드를 호출하는 것 뿐이다
start() 메소드를 호출하면, 스레드가 작업을 실행하는데 필요한 콜 스택을 생성한 다음 run()을 호출해서 그 스택 안에 run()을 저장할 수 있도록 해준다.
새로운 콜 스택을 만들어 작업을 해야 스레드 일처리가 되는 것이기 때문에 start() 메소드를 써야한다
스레드의 상태는 5가지가 있다

| 상태 | 열거 상수 | 설명 |
|---|---|---|
| 객체 생성 | NEW | 스레드가 생성되고 아직 start()가 호출되지 않은 상태 |
| 실행 대기 | RUNNABLE | 실행 중 또는 실행 가능 상태 |
| 일시 정지 | BLOCKED | 동기화 블럭에 의해 일시정지된 상태(lock이 풀릴 때까지 기다림) |
| 일시 정지 | WAITING TIME_WAITING | 실행가능하지 않은 일시정지 상태 |
| 종료 | TERMINATED | 스레드 작업이 종료된 상태 |
스레드로 구현하는 것이 어려운 이유는 바로 동기화와 스케줄링 때문이다
스케줄링과 관련된 메소드
sleep(), join(), yield(), interrupt() ...
모든 스레드가 종료된 후에 main 메서드를 종료하고 싶은 경우(스레드가 모두 종료된 후 그 다음 작업을 진행해야 하는 경우) join 메소드를 사용하면 된다.
멀티스레드 환경에서 여러 스레드가 하나의 공유자원에 동시에 접근하지 못하도록 막는것
멀티 스레드 프로그래밍에서 동기화는 필수적이다
동기화가 필요한 이유는, 여러 스레드가 같은 프로세스 내의 자원을 공유하면서 작업할 때 서로의 작업이 다른 작업에 영향을 주기 때문이다.
스레드의 동기화를 하기 위해서 임계 영역(critical section)과 잠금(lock)을 활용한다.
임계영역(동시에 리소스를 사용할 수 없는 구역)을 지정하고,
임계영역을 가지고 있는 lock을 단 하나의 스레드에게만 빌려준다. 따라서 임계구역 안에서 수행할 코드가 완료되면 lock을 반납해야한다.
synchronized를 활용해 임계영역을 설정할 수 있다.
서로 다른 두 객체가 동기화를 하지 않은 메소드를 같이 오버라이딩해서 이용하면 두 스레드가 동시에 진행되므로 원하는 출력 값을 얻지 못한다
=> 오버라이딩되는 부모 클래스의 메소드에 synchronized 키워드로 임계영역을 설정해주면 해결할 수 있다
//synchronized : 스레드의 동기화. 공유 자원에 lock
public synchronized void saveMoney(int save){ // 입금
int m = money;
try{
Thread.sleep(2000); // 지연시간 2초
} catch (Exception e){
}
money = m + save;
System.out.println("입금 처리");
}
public synchronized void minusMoney(int minus){ // 출금
int m = money;
try{
Thread.sleep(3000); // 지연시간 3초
} catch (Exception e){
}
money = m - minus;
System.out.println("출금 완료");
}
스레드가 서로 협력관계일 경우에는 무작정 대기시키는 것으로 올바르게 실행되지 않기 때문에 사용한다.
wait() : 스레드가 lock을 가지고 있으면, lock 권한을 반납하고 대기하게 만듦
notify() : 대기 상태인 스레드에게 다시 lock 권한을 부여하고 수행하게 만듦
이 두 메소드는 동기화 된 영역(임계 영역)내에서 사용되어야 한다
동기화 처리한 메소드들이 반복문에서 활용된다면, 의도한대로 결과가 나오지 않는다
=> wait()와 notify()를 try-catch 문에서 적절히 활용해 해결할 수 있다
/**
* 스레드 동기화 중 협력관계 처리작업 : wait() notify()
* 스레드 간 협력 작업 강화
*/
public synchronized void makeBread(){
if (breadCount >= 10){
try {
System.out.println("빵 생산 초과");
wait(); // Thread를 Not Runnable 상태로 전환
} catch (Exception e) {
}
}
breadCount++; // 빵 생산
System.out.println("빵을 만듦. 총 " + breadCount + "개");
notify(); // Thread를 Runnable 상태로 전환
}
public synchronized void eatBread(){
if (breadCount < 1){
try {
System.out.println("빵이 없어 기다림");
wait();
} catch (Exception e) {
}
}
breadCount--;
System.out.println("빵을 먹음. 총 " + breadCount + "개");
notify();
}
조건을 만족하지 않을 시 wait(), 만족 시 notify()를 받아 수행한다