✔️ 멀티 스레드
: 하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행
- 각 스레드가 자신이 속한 프로세스의 메모리를 공유
✔️ 멀티 프로세스
: 여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행
- 각 프로세스가 독립적인 메모리를 가지고 별도로 실행
➡️ 둘 모두 여러 흐름을 동시에 수행하다는 공통점을 가지고 있다.
⭐️ 장점
❌ 단점
synchronization
)에주의해야 한다.➡️ 우리가 살펴 볼 것은 병행 프로그래밍
✅ 메인 스레드(Main Thread)
- 모든 자바 애플리케이션은 메인 스레드(main thread)가
main()
메소드를 실행하면서 시작된다.
👉 이걸 Race Condition(경쟁 상태) 라고 부른다. 🏁
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for(int i=0; i<1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for(int i=0; i<1000; i++) counter.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("최종 count = " + counter.getCount());
}
}
count
는 2000이 나와야 할 것 같지만, 실제로는 1900~1999 사이 랜덤한 값이 나온다.count++
는 사실 한 줄로 보이지만 내부적으로 이렇게 3단계이다 👇1️⃣ count
값을 메모리에서 읽음
2️⃣ 값에 +1
함
3️⃣ 다시 메모리에 저장
🚨 근데 두 스레드가 동시에 1~3단계를 수행하면
서로의 변경 내용이 덮여버리는 문제가 생긴다 → 결과값 꼬임
✅ 여러 스레드가 공유 자원(객체, 변수 등) 에 동시에 접근할 때
데이터가 꼬이지 않도록 “한 번에 하나의 스레드만” 접근하도록 잠그는(lock) 기능.
synchronized
는 “한 스레드가 자물쇠를 걸고 작업하는 동안, 다른 스레드는 문 앞에서 대기한다.” 🚪🔒형태 | 설명 | 예시 |
---|---|---|
메서드 동기화 | 메서드 전체에 lock을 걸어 한 번에 하나의 스레드만 실행 가능 | public synchronized void run() |
블록 동기화 | 특정 코드 블록만 잠금 (효율적) | synchronized(this) { ... } |
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
class Counter {
private int count = 0;
public void increment() {
synchronized(this) { // 현재 객체에 lock 걸기
count++;
}
}
}
this
는 현재 객체,public static synchronized void printStatic() {
...
}
또는
synchronized(MyClass.class) {
...
}
자바는 내부적으로 모니터 락(Monitor Lock) 이라는 개념을 쓴다.
어떤 객체에 synchronized
가 걸리면
→ JVM이 그 객체에 대한 모니터 락(Monitor Lock) 을 획득해야 함
락을 가진 스레드만 코드 실행 가능
끝나면 락을 반납 (lock release
)
문제 | 설명 | 해결 방법 |
---|---|---|
Deadlock(교착 상태) | 두 스레드가 서로의 락을 기다려서 무한 대기 | 락 순서 동일하게 유지 |
성능 저하 | 락 걸린 영역이 많으면 병렬성 떨어짐 | 필요한 부분에만 잠금 |
객체 기준 잠금 | 객체가 다르면 synchronized 무의미 | 같은 객체 공유 시에만 의미 있음 |
✔️ 다른 일반 스레드의 작업을 돕는 보조적인 역할을 하는 스레드
✔️ 일반 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료됨
데몬 스레드는 일반 스레드가 모두 종료되면 더는 할 일이 없으므로, 데몬 스레드 역시 자동으로 종료된다.
이러한 데몬 스레드는 백그라운드 작업에서 활용된다.
포그라운드에서 작업이 끝날 때 백그라운드도 같이 종료돼야 하는데 이것을 데몬스레드로 처리한다.
또한 일정 시간마다 자동으로 수행되는 저장 및 화면 갱신, 가비지 컬렉터 등에도 이용되고 있다.
📎 가비지 컬렉터(gabage collector)
: 프로그래머가 동적으로 할당한 메모리 중 더 이상 사용하지 않는 영역을 자동으로 찾아내어 해제해 주는 데몬 스레드
데몬 스레드의 생성 방법과 실행 방법은 모두 일반 스레드와 같다.
- void setDaemon(boolean ..): 스레드를 데몬 스레드 또는 일반 스레드로 변경
- boolean isDaemon(): 스레드가 데몬 스레드인지 확인. 맞으면 true 반환
❗️setDaemon(boolean ..)
은 반드시 start()
메서드를 호출하기 전에 실행되어야 한다. 그렇지 않으면 IllegalThreadStateException
이 발생한다.
public class NormalThreadTest {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
try {
System.out.println("MyThread 시작");
Thread.sleep(5000); //
System.out.println("MyThread 종료");
} catch (Exception e) {
}
}
};
t.start();
System.out.println("main() 종료");
}
}
main() 종료
MyThread 시작
MyThread 종료
모든 스레드가 종료되어야 프로그램이 종료된다는 것을 확인 할 수 있다.
public class DeamonThreadTest {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
try {
System.out.println("MyThread 시작");
Thread.sleep(5000);
System.out.println("MyThread 종료");
} catch (Exception e) {}
}
};
// 반드시 start() 호출 전에 사용해야 한다!
// 데몬스레드
t.setDaemon(true);
t.start();
System.out.println("main() 종료");
}
}
main() 종료
MyThread 시작
일반 스레드와 비교했을 때, 주 스레드가 종료되자 같이 종료되어버렸다.
✔️ 멀티스레드의 순서를 정하는 것
: 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링 하는 것
: setPriority()
메소드를 사용하여 우선순위를 설정
: 우선순위는 1에서 10까지 부여할 수 있고 1이 가장 낮고 10이 가장 높다.
// Thread 상속
class SomeThread extends Thread {
public SomeThread(String name) {
super(name);
}
@Override
public void run() {
String name = this.getName();
for(int i = 0; i < 10; i++) {
System.out.println(name + " is working");
try {
Thread.sleep(500);
} catch (Exception e) {}
}
}
}
public class RunningTest {
// main thread는 우선순위가 NORM_PRIORITY(5) 이다.
public static void main(String[] args) {
SomeThread t1 = new SomeThread("A");
SomeThread t2 = new SomeThread("B");
SomeThread t3 = new SomeThread("C");
t1.setPriority(Thread.MIN_PRIORITY); // = 1
t2.setPriority(Thread.NORM_PRIORITY); // = 5
t3.setPriority(Thread.MAX_PRIORITY); // = 10
t1.start();
t2.start();
t3.start();
}
}
A is working
B is working
C is working
B is working
A is working
C is working
A is working
B is working
C is working
B is working
C is working
A is working
C is working
A is working
B is working
A is working
B is working
C is working
A is working
B is working
C is working
B is working
C is working
A is working
A is working
B is working
C is working
A is working
C is working
B is working
우선순위가 높은 스레드를 무조건 실행하게 하는 것이 아니고 확률이 약간 더 올라간다.
100%가 아니기 때문에 우선순위에 의존해서 선택하지 않는다.
C가 먼저 동작하는 경우는 없지만 아예 없는 것도 아니다.
즉, 우선 순위를 제일 높게 설정한다고 항상 먼저 실행됨을 보장 할 수는 없다.
시간 할당량(Time Slice)을 정해서 하나의 스레드를 정해진 시간만큼 실행하고 다시 다른 스레드를 실행하는 방식