쓰레드(Thread), ThreadLocal, Runnable
- 프로세스 : 실행중인 프로그램
- 멀티태스킹 : 다중 프로세스 실행
- 쓰레드 : 프로세스 내에서 작업 단위
- 멀티쓰레딩 : 한 프로세스 내에서 다중 쓰레드 실행
쓰레드
- Thread 클래스를 상속
- 자바의 단일상속에 의해 다른 클래스를 상속받을수 없음
- 인터페이스를 사용하는 방법에 비해 유연성이 떨어짐
-
Runnable 인터페이스 구현(추천)
- 쓰레드간의 데이터를 공유해야 할 때 인터페이스로 구현해서 연결시킴
- Thread와 실행할 코드(=
run()
)를 분리 할 수 있어 가독성 증대
- 쓰레드는 순서와 실행 시간을 모두 보장하지 않는다
- 어플리케이션은 모든 사용자 쓰레드가 종료되면 종료된다
- 메인 쓰레드가 종료된다고 종료되는 것이 아님
- 어느 사용자 쓰레드 스택에 메서드 하나 없이 비게 되면 해당 쓰레드는 종료
Runnable 인터페이스
- 추상메서드 run() 정의
public interface Runnable {
public abstract void run();
}
Thread 클래스
public class Thread implements Runnable {
...
private volatile String name;
private int priority;
private Runnable target;
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
public static native Thread currentThread();
public synchronized void start() {
...
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {}
}
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
-
start()
- 쓰레드를 실행시킬 때 사용하는 인스턴스 메서드(Thread 클래스에 구현되어 있음)
- 하나에 쓰레드에 대해 한번만 호출 가능(다시 호출하기 위해서는 new 연산자로 다시 만들어 줘야함)
- 새로운 쓰레드를 호출스택에 생성한 다음
run()
을 호출 - 실행 대기 상태로 전환시키며 실행 중인 쓰레드가 없으면 바로 실행시킴
-
항상
start()
를 통해 쓰레드를 실행시켜야 한다- 이 메서드는 신규 쓰레드를 생성하고 로직을 수행 요청하는 특별한 메서드
-
start()
대신run()
메서드를 실행시키면 신규 쓰레드가 아닌 메인 쓰레드가 실행하게 됨 (그냥 메서드 호출과 동일)
-
run()
- 쓰레드를 실행시키는 것이 아닌 run()에 구현된 메서드를 호출하는 것.
- 쓰레드를 실행시키는 start()에 의해 호출당함.
-
currentThread()
- 현재 쓰레드 표기 ex)
Thread[name, priority, group]
- 현재 쓰레드 표기 ex)
쓰레드 사용
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.CurrentThread().getName() + ": run()");
}
}
HelloRunnable runnable = new HelloRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
데몬 쓰레드
- 사용자 쓰레드를 돕기위한 보조역할 쓰레드
-
setDaemon(true)
로 설정함. 이 메서드는 꼭start()
호출 전에 실행 되어야함. - 보조적인 역할만 하기 때문에 사용자 쓰레드가 종료되면 자동으로 종료됨.
public class DaemonThreadMain {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + ": main() start");
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true); // start() 호출 전 데몬 스레드 설정
daemonThread.start();
System.out.println(Thread.currentThread().getName() + ": main() end");
}
static class DaemonThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": run() start");
}
}
}
스케줄링(실행제어)
생성 –①–>실행대기<—-②—->실행 –⑤–>소멸
└<-④-일시정지<-③ ┘
- start() : 실행대기 or 바로 실행
- yield() : 다시 실행대기상태가 되고 다음 쓰레드가 실행됨
- suspend(), sleep(), wait(), join(), I/O block : 일시정지 상태로 전환 ex) 사용자의 입력을 기다림.
- resume(), notify(), interrupt(), timeout : 일시정지에서 벗어나 다시 실행대기로 전환
- stop() : 쓰레드 소멸
동기화(Synchronization)
- 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 함.
- lock을 갖은 하나의 쓰레드만 임계 영역 내의 코드 수행
- synchronized로 임계 영역 설정.
- 동기화 메서드 : 메서드 선언부에 synchronized 설정
synchronized void withdraw() {
if (money <= 0) {
return;
}
money -= 10000;
System.out.println(Thread.currentThread().getName() + "출금. 잔액 : " + money);
}
- 동기화 블록 : synchronized 블록 지정
public void withdraw() {
synchronized(this) {
if (money <= 0) {
return;
}
money -= 10000;
System.out.println(Thread.currentThread().getName() + "출금. 잔액 : " + money);
}
- wait() : 사용 중인 lock을 강제로 반납시킴.
- notify() : 중단 됐던 쓰레드가 lock을 다시 얻어 실행됨(재진입)
ThreadLocal
- 각 쓰레드마다 별도의 저장소를 사용할 수 있게 해주는 클래스 (쓰레드 전용 저장소)
- 전역변수/클래스변수를 쓰레드마다 다르게 접근할 수 있어 동시성 문제를 해결 할 수 있다
public class ThreadLocalExam {
public static ThreadLocal<String> myThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
myThreadLocal.set("Hello World!");
String myThreadLocalValue = myThreadLocal.get();
myThreadLocal.remove();
}
}
-
new ThreadLocal()
: 생성자 -
set()
: 값 저장 -
get()
: 값 조회 -
remove()
: ThreadLocal에 할당된 값 삭제 -
remove()
를 반드시 실행하여ThreadLocal
을 비워야 한다- 쓰레드 풀과 같은 환경에서 ThreadLocal을 사용하고 비워주지 않으면 데이터가 남아있게 되어 재사용시 유효하지 않은 데이터를 사용할 수도 있게 된다