🤔 Thread 공부하기 전에 프로세스부터 알고 넘어가기
- 운영체제의 관점에서 실행 중인 독립적인 프로그램이다.
- 메모장, 크롬, 이클립스를 실행했다면?
- 3개의 어플리케이션을 실행한 상태이며, 각 어플리케이션은 별도의 독립적인 프로세스가 존재하기 때문에 멀티 태스킹이 가능하다.
- 자원과 스레드로 구성되어 있다.
🤷♀️ Thread란?
- 명령어들의 집합이다.
- 프로세스 내에서 실제 작업을 수행하며, 모든 프로세스는 최소 1개 이상의 스레드를 가진다.
- 대부분 이를 메인 스레드라고 하며, 부가적인 스레드를 생성하여 병렬 작업을 수행할 수 있다.
- 자원을 효율적으로 사용할 수 있다.
- 여러 스레드가 동시에 실행되어 CPU 시간 및 메모리 등 자원을 효과적으로 활용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 하나의 스레드가 블록되어 작업을 수행하는 동안에도 다른 스레드들이 실행되므로 전체적으로 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
- 각 스레드가 특정 작업에 집중할 수 있어 코드가 간결해지고 유지보수가 쉬워진다.
- 동기화(synchronization)에 주의해야 한다.
- 자원과 2개의 A 스레드, B 스레드가 있을 때
- CPU → A 스레드에 시간을 할당하고, 주어진 시간이 끝나면 중간에 멈췄다가,
B 스레드에 시간을 할당하고, 주어진 시간이 끝나면 다시 A 스레드로... 왔다리갔다리 한다.- 이때, CPU 속도가 빠르기 때문에 동시에 실행되는 것처럼 보인다.
⛔⛔ 처리 도중 자원의 데이터가 변경되면 A 스레드와 B 스레드는 다른 데이터를 처리하게 된다.- 교착 상태에 주의해야 한다.
- 교착 상태란, 둘 이상의 프로세스나 스레드가 서로 상대방의 작업이 끝나기를 기다리며 무한히 대기하는 상태다.
- 각 스레드가 효율적으로, 고르게 실행될 수 있게 해야 한다.
- Thread 클래스를 상속한 class를 작성한 후, 인스턴스 생성
run()
메소드를 재정의하여 코드 작성
- 재정의한 내용이 스레드를 처리할 때 실행
- 인스턴스의
start()
메소드를 호출하여 실행
❗java
는 단일 상속만을 지원하기 때문에, 이미 다른 클래스를 상속받고 있을 때에는 이 방법을 사용할 수 없다.
- Runnable 인터페이스를 구현한 class를 작성한 후, 인스턴스 생성
run()
메소드를 재정의하여 코드 작성
- 재정의한 내용이 스레드를 처리할 때 실행
- 인스턴스를 Thread 클래스의 인스턴스를 생성할 때 생성자의 인수값으로 넘겨줌
- 인스턴스의
start()
메소드를 호출하여 실행
❗ Runnable 인터페이스를 구현할 경우, 단일 상속과는 상관없이 멀티 스레드를 만들 수 있다.
👉 인터페이스는 하나의 클래스가 여러개를 구현할 수 있기 때문에!
package kr.or.ddit.basic;
public class ThreadTest14 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start();
MyRunner runner = new MyRunner();
Thread thread2 = new Thread(runner);
thread2.start();
}
}
// 1. Thread 클래스 상속
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("*");
try {
Thread.sleep(1000); // 단위는 밀리세컨드 (1초 -> 1000)
} catch (InterruptedException e) {
}
}
}
}
// 2. Runnable 인터페이스 구현
class MyRunner implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("#");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
😶 Thread.sleep()을 try-catch문으로 처리하는 이유
sleep()
메소드는 스레드를 지정된 시간동안 일시중지 시키는데, 중간에 다른 스레드가 일시중지 된 스레드를interrupt()
메소드를 통해 깨우려고 할 때InterruptedException
이 발생할 수 있다.- 따라서 코드의 안정성과 적절한 대응을 하기 위해 예외 처리를 해주는 것이 좋다.
run()
메소드를 재정의했는데 start()
메소드를 호출하는 이유start()
메소드를 호출하면 새로운 스레드가 생성되고, 내부적으로 해당 스레드가 실행되는데, 그 안에서 run()
메소드가 호출되기 때문이다. 직접적으로 run()
메소드를 실행할 경우 일반적인 단순 메소드 호출이 되기 때문에 single thread
와 동일하게 처리된다. main()
메소드의 실행과는 병렬적으로 동작되어 main()
메소드가 실행되는 동안 다른 스레드를 생성하고 실행할 수 있다.
- 일반 스레드의 보조 역할이며, 일반 스레드가 모두 종료되면 자동으로 종료된다.
- 일반 스레드와 만드는 방법은 동일함
- 스레드 실행 전 (
start()
호출 전) 데몬 스레드라고 명시해야 하고,start()
이후 설정시 Exception 발생
setDaemon()
→ true: 데몬스레드, false: 일반스레드- 데몬 스레드 확인
isDaemon()
→ true: 데몬스레드, false: 일반스레드
상태 | 설명 |
---|---|
NEW | 스레드 객체 생성 후 start() 호출 전 |
RUNNABLE | 실행 중 / 실행 가능한 상태 |
BLOCKED, WAITING, TIMED_WAITING | 일시정지된 상태 (sleep(), join(), suspend(), wait(), I/O block), 주어진 시간이 끝나면 일시정지를 마치고 대기열로 감 (resume(), notify(), interrupt()) |
TERMINATED | 스레드 작업 종료 및 스레드 객체 소멸 |
suspend()
: 스레드 일시 정지
resume()
: suspend()
에 의해 일시 정지 상태인 스레드를 실행 대기 상태로
join()
: 지정된 시간동안 스레드 실행, 지정된 시간이 지나거나 작업 종료시 join()
을 호출한 스레드로 돌아가서 실행
interrupt()
: sleep()
, join()
에 의해 일시 정지된 스레드를 실행 대기 상태로, InterruptException 발생시 일시 정지 상태를 벗어남
sleep()
: 지정된 시간동안 스레드 일시 정지, 지정된 시간이 지나면 자동으로 실행 대기 상태
stop()
yield()
: 실행 중 다른 스레드에게 양보 후 실행 대기 상태로public void setSd(ShareData sd) {
this.sd = sd;
}
public PrintPIThread(ShareData sd) {
this.sd = sd;
}
Vector, Hashtable 등과 같이 예전부터 존재하던 Collection 객체들은 내부에 동기화 처리가 되어 있다. 새로 구성된 Collection들은 동기화 처리가 되어 있지 않다. 동기화가 필요한 프로그램에서 이런 Collection들을 사용하려면 동기화 처리 후 사용해야 한다.